首页 > 解决方案 > 您可以使用 jquery 为 svg 过滤器镜面照明设置动画来简化整数吗?

问题描述

我正在使用feSpecularLightingsvg 过滤器,并且正在fePointLight为鼠标在桌面上移动的位置设置动画。

<feSpecularLighting in="blur" surfaceScale="15" specularConstant="2.5" specularExponent="200" result="specOut" lighting-color="white">
    <fePointLight x="-10000" y="-10000" z="8000" />
</feSpecularLighting>

我似乎找不到使用 CSS 过渡效果来缓解xy属性更改的方法,我认为 svg 过滤器不会像这样工作。所以我想知道是否可以简化xand的整数变化y

请参阅下面的工作示例,没有缓动x属性y值。

// lighting effect variables
let lightDist = 10000;
let currentLightPos = {
  x: -lightDist,
  y: -lightDist
};
let currentMousePos = {
  x: -1,
  y: -1
};
let windowWidth = $(window).outerWidth();
let windowHeight = $(window).outerHeight();
let ratio = {
  x: lightDist / (windowWidth / 2),
  y: lightDist / (windowHeight / 2)
};

// when the window is resized
$(window).on('resize', function() {

  // update lighting effect variables
  windowWidth = $(window).outerWidth();
  windowHeight = $(window).outerHeight();
  ratio = {
    x: lightDist / (windowWidth / 2),
    y: lightDist / (windowHeight / 2)
  };

});

// when the mouse is moved (desktop)
$(document).on('mousemove', function(e) {

  // get our current mouse position
  currentMousePos.x = e.pageX;
  currentMousePos.y = e.pageY;

  // if mouse is on right side of window
  if (currentMousePos.x > (windowWidth / 2)) {

    // calculate the positive light x position
    currentLightPos.x = ratio.x * (currentMousePos.x - (windowWidth / 2));

    // if mouse is on left side of window
  } else if (currentMousePos.x < (windowWidth / 2)) {

    // calculate the negative light x position
    currentLightPos.x = -lightDist + (ratio.x * currentMousePos.x);

  }

  // calculate the negative light y position
  // if mouse is on bottom side of window
  if (currentMousePos.y > (windowHeight / 2)) {

    // calculate the positive light y position
    currentLightPos.y = ratio.y * (currentMousePos.y - (windowHeight / 2));

    // if mouse is on top side of window
  } else if (currentMousePos.y < (windowHeight / 2)) {

    // calculate the negative light y position
    currentLightPos.y = -lightDist + (ratio.y * currentMousePos.y);

  }
  
  // console.log(currentLightPos.x, currentLightPos.y);

  // update shine filter fePointLight attributes
  $('fePointLight').attr({
    'x': currentLightPos.x,
    'y': currentLightPos.y
  });

});
.circle {
  width: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50" filter="url(#shine)" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
    <filter id="shine" filterUnits="objectBoundingBox" x="-10%" y="-10%" width="150%" height="150%">
        <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur" />
        <feSpecularLighting in="blur" surfaceScale="15" specularConstant="2.5" specularExponent="200" result="specOut" lighting-color="white">
            <fePointLight x="-10000" y="-10000" z="8000" />
        </feSpecularLighting>
        <feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut2" />
        <feComposite in="SourceGraphic" in2="specOut2" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" />
    </filter>
</svg>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


为了模拟我遇到的问题,如果您将鼠标移到示例窗口之外,然后将鼠标移回窗口的不同位置,您会注意到点光源会立即跳到新计算的位置。

我想知道是否有可能缓解这一点,所以xy整数总是缓和到它们新计算的位置,而不是如果你将鼠标从另一侧带入窗口,而不是立即跳到新位置?

标签: jquerysvg

解决方案


(除此之外:虽然您的问题是“整数”,但<fePointLight>属性也采用实数。所以这不会损害流畅的缓动。)

您可以通过两个步骤使用声明性 SMIL 动画来做到这一点:首先,您为两个声明两个<animate>元素x和. 确保此动画可以随时重新启动,即使动画当前正在运行。保留动画结束后的值。y<fePointLight>restart="always"fill="freeze"

但主要的技巧是设置begin="indefinite". 这会推迟动画的开始,直到它被 Javascript API 调用触发。

<fePointLight x="-10000" y="-10000" z="8000">
    <animate id="anim_x" attributeName="x"
             begin="indefinite" dur="0.5s" restart="always"
             from="-10000" to="-10000" fill="freeze" />
    <animate id="anim_y" attributeName="y"
             begin="indefinite" dur="0.5s" restart="always"
             from="-10000" to="-10000" fill="freeze" />
</fePointLight>

我已将缓动功能保留为默认linear值。对于更复杂的行为,语法比 CSS 缓动函数更复杂一些。类似的东西ease-in-out看起来像这样:

    <animate id="anim_x" attributeName="x"
             begin="indefinite" dur="0.5s" restart="always"
             calcMode="spline" values="-10000;-10000" keyTimes="0;1"
             keySplines=".5 0 .5 1" fill="freeze" />

现在,在每次鼠标移动时,您的事件监听器都需要做三件事:

  • 将属性设置为/属性<animate from>的当前动画值。这使得动画从当前值的任何位置开始,即使在运行动画期间也是如此。<fePointLight> element.x.animValelement.y.animVal
  • <animate to>属性设置currentLightPos为将动画移向其目标的值。
  • 使用该方法启动动画<fePointLight> element.beginElement(),取消任何当前正在运行的动画。

(如果您使用的是样条曲线,则必须设置values属性而不是fromand to。)

  const pointLight = $('fePointLight');
  const lightX = $('#anim_x');
  const lightY = $('#anim_y');

  lightX.attr({
    from: pointLight.prop('x').animVal,
    to: currentLightPos.x
  });
  lightX[0].beginElement();
  lightY.attr({
    from: pointLight.prop('y').animVal,
    to: currentLightPos.y
  });
  lightY[0].beginElement();

这让点光源“追逐”当前鼠标位置,直到鼠标移动停止并且动画有机会运行到其结束。

// lighting effect variables
let lightDist = 10000;
let currentLightPos = {
  x: -lightDist,
  y: -lightDist
};
let currentMousePos = {
  x: -1,
  y: -1
};
let windowWidth = $(window).outerWidth();
let windowHeight = $(window).outerHeight();
let ratio = {
  x: lightDist / (windowWidth / 2),
  y: lightDist / (windowHeight / 2)
};

const pointLight = $('fePointLight');
const lightX = $('#anim_x');
const lightY = $('#anim_y');

// when the window is resized
$(window).on('resize', function() {

  // update lighting effect variables
  windowWidth = $(window).outerWidth();
  windowHeight = $(window).outerHeight();
  ratio = {
    x: lightDist / (windowWidth / 2),
    y: lightDist / (windowHeight / 2)
  };

});

// when the mouse is moved (desktop)
$(document).on('mousemove', function(e) {

  // get our current mouse position
  currentMousePos.x = e.pageX;
  currentMousePos.y = e.pageY;

  // if mouse is on right side of window
  if (currentMousePos.x > (windowWidth / 2)) {

    // calculate the positive light x position
    currentLightPos.x = ratio.x * (currentMousePos.x - (windowWidth / 2));

    // if mouse is on left side of window
  } else if (currentMousePos.x < (windowWidth / 2)) {

    // calculate the negative light x position
    currentLightPos.x = -lightDist + (ratio.x * currentMousePos.x);

  }

  // calculate the negative light y position
  // if mouse is on bottom side of window
  if (currentMousePos.y > (windowHeight / 2)) {

    // calculate the positive light y position
    currentLightPos.y = ratio.y * (currentMousePos.y - (windowHeight / 2));

    // if mouse is on top side of window
  } else if (currentMousePos.y < (windowHeight / 2)) {

    // calculate the negative light y position
    currentLightPos.y = -lightDist + (ratio.y * currentMousePos.y);

  }
  
  // console.log(currentLightPos.x, currentLightPos.y);

  // update shine filter fePointLight attributes
  lightX.attr({
    from: pointLight.prop('x').animVal,
    to: currentLightPos.x
  });
  lightX[0].beginElement();
  lightY.attr({
    from: pointLight.prop('y').animVal,
    to: currentLightPos.y
  });
  lightY[0].beginElement();

});
.circle {
  width: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50" filter="url(#shine)" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
    <filter id="shine" filterUnits="objectBoundingBox" x="-10%" y="-10%" width="150%" height="150%">
        <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur" />
        <feSpecularLighting in="blur" surfaceScale="15" specularConstant="2.5" specularExponent="200" result="specOut" lighting-color="white">
          <fePointLight x="-10000" y="-10000" z="8000">
            <animate id="anim_x" attributeName="x"
                     begin="indefinite" dur="0.5s" restart="always"
                     from="-10000" to="-10000" fill="freeze" />
            <animate id="anim_y" attributeName="y"
                     begin="indefinite" dur="0.5s" restart="always"
                     from="-10000" to="-10000" fill="freeze" />
          </fePointLight>
        </feSpecularLighting>
        <feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut2" />
        <feComposite in="SourceGraphic" in2="specOut2" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" />
    </filter>
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


推荐阅读