使用javascript for循环更改svg元素属性

时间:2018-05-24 21:57:36

标签: javascript html web

当我遇到这个问题时,我正在练习网络编程。 问题是当我尝试在for循环中重复增加圆的半径时,它不会在每次迭代后显示变化的结果,尽管它在最后一次迭代时显示结果,即当我达到40时,圆圈与显示半径40。那么这里的问题是什么?

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function strokeincr(){
var circl = document.getElementById("circ");
var i;
for(i=10;i<=40;i++)
{
alert("for i="+i);//this was just to see the value of i changing.
            circl.setAttribute("r",i);
       }
    }

</script>

<style>
    #div1 svg{
        width:200px;
        height: 200px;
        background-color:pink;
    }

</style>
</head>

<body>
<div id="div1">
<svg>
  <circle id="circ" cx="100" cy="100" r="10" fill="green"></circle>
</svg>
</div> 
<button onclick="strokeincr()"> click me</button>
</body>
</html>

4 个答案:

答案 0 :(得分:1)

此处的问题与浏览器如何更新屏幕以及事件的工作方式有关。浏览器有一个叫做“事件循环”的东西。浏览器等待某些事件发生,例如用户单击按钮,然后执行相关代码。修改文档的调用(如setAttribute调用)不会直接导致屏幕更新。相反,他们正在更新DOM(文档对象模型),它定义了浏览器应该在屏幕上显示的内容。

只有在所有事件处理代码完成后,屏幕才会更新。因此,您从10循环到40,更新圆的半径。但浏览器只是在循环结束后才会检查值。所以你只能看到最终的价值。

如@ zfrisch的回答所述,解决此问题的一种方法是使用浏览器的事件调度功能。这些功能允许您在稍后的时间要求浏览器触发事件​​(并使用您选择的代码处理它)。

您可以使用三种主要的日程安排功能:setTimeout(callback, delayInMs)setInterval(callback, intervalInMs)requestAnimationFrame(callback)。您可以查看文档以获取进一步说明,但一般来说,对于动画,您应该使用requestAnimationFrame

requestAnimationFrame基本上要求浏览器稍后执行某些代码,之后它已呈现所有内容并准备呈现新内容。

使用requestAnimationFrame,您的示例可能如下所示:

<!DOCTYPE html>
<html>

<head>
  <script type="text/javascript">
    function strokeincr() {
      // Note the use of const instead of var. It indicates a variable that we won't change, and ensures that we don't change it.
    
      const circl = document.getElementById("circ");
      
      // We need to know how long our animation should last, so we can compute where we are.
      // Note that since the varaible contains milliseconds, we label it as such for clarity.
      const animationDurationMs = 1000;
      
      // The starting time of the animation (in milliseconds since the epoch), so we can compute where we are in the animation later.
      const startTimeMs = Date.now();
      
      // Define the starting and ending values for the animation for later computation
      const startRadius = 10;
      const endRadius = 40;
      
      function incrementAnimation() {
        // Get the now-current time so we can compute the animation progress
        const currentTimeMs = Date.now();
        
        // Compute where we are in the animation as a fraction, ensuring we don't go over 1, using Math.min
        const animationFraction = Math.min(
            1,
            (currentTimeMs - startTimeMs) / animationDurationMs
          );
        
        // Compute what the current radius should be, given where we are in the animation, using liner interpolation
        const currentRadius = startRadius + (endRadius - startRadius) * animationFraction;
        
        // Update the circle with the new radius
        circl.setAttribute("r", currentRadius);
        
        // Check if there is more animating to do, and if so, request that this function is run again after the screen is updated.
        if (animationFraction < 1)
          requestAnimationFrame(incrementAnimation);
      }
      
      // Start the animation by calling the animation function directly.
      incrementAnimation();
    }
  </script>

  <style>
    #div1 svg {
      width: 200px;
      height: 200px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <div id="div1">
    <svg>
      <circle id="circ" cx="100" cy="100" r="10" fill="green"></circle>
    </svg>
  </div>
  <button onclick="strokeincr()">click me</button>
</body>
</html>

答案 1 :(得分:0)

好吧,首先警报会停止进程,但更重要的是for loop告诉程序在它的大括号之间运行代码,它会尽可能快速有效地执行。正如@YonaAppletree指出的那样,尽管没有在官方规范中编写,但大多数浏览器(如果不是全部)都实现了相同或平凡不同的处理规则,并且在for循环期间不允许重新绘制视图。这是一种节省资源的优化,实际上它与最终用户的观点没有多大区别,因为即使它没有做到这一点,代码通常会比人眼更快地处理能辨别出来。这意味着它是一个很好的优化,但它也意味着您当前的代码无法按预期工作。

我们可以做的是设置一个间隔计时器,它会在i等于或大于40之后自行清除。间隔需要两个参数

setInterval( function , time in ms )

ms是毫秒。 1000毫秒等于1秒。提供的功能将以指定的间隔反复重复,直到间隔被清除。例如:

setInterval( function() { console.log("hi!") }, 1000)

将打印&#34; hi!&#34;每1000毫秒到你的控制台,相当于1秒。这永远不会停止,因为它永远不会清除。

通过将此函数分配给变量,我们确保在i达到40或更高时我们可以清除它(停止函数)。

这是为变量分配间隔的方法:

var myIntervalTimer = setInterval(function, time in ms)

清除您拨打clearInterval( name of interval )的间隔。例如:

clearInterval(myIntervalTimer)

然后我们将其插入您已经建立的代码中,瞧!

var i = 10;
var myIntervalTimer = setInterval(function() {
             console.log("i is" + i);
            circl.setAttribute("r",i);
            if(i >= 40) clearInterval(myIntervalTimer);
            i++;
}, 250);

我们现在有一个定时器,每隔四分之一秒(250ms)会增加圆的半径,如果i等于或大于40,则会清除间隔定时器。

&#13;
&#13;
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function strokeincr(){
var circl = document.getElementById("circ");
var i = 10;
var myIntervalTimer = setInterval(function() {
            circl.setAttribute("r",i);
            if(i >= 40) clearInterval(myIntervalTimer);
            i++;
}, 250);
}
</script>

<style>
    #div1 svg{
        width:200px;
        height: 200px;
        background-color:pink;
    }

</style>
</head>

<body>
<div id="div1">
<svg>
  <circle id="circ" cx="100" cy="100" r="10" fill="green"></circle>
</svg>
</div> 
<button onclick="strokeincr()"> click me</button>
</body>
</html>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

单击按钮时将执行整个循环。如果您想在每次单击按钮时增加圆的半径,则必须采用不同的方法。

let publickey : SecKey = 
    "# -----BEGIN PUBLIC KEY-----  some key  ----END PUBLIC KEY----- #" as! SecKey

答案 3 :(得分:0)

这是一个非常有趣的问题。我会尽力回答它。

由于浏览器优化,您遇到了存在的意外行为。它与重新绘制有关。您正在循环的每一步更改圆的半径。如果浏览器必须在每次迭代时重新绘制屏幕,​​那将是一项非常苛刻的操作。

那么浏览器供应商做了什么?他们(显然)推迟了重复,直到循环结束。有道理吗?

当您开始使用警报暂停循环时,这突然变得明显(请注意DOM中的值在每次迭代时的确如何变化,但屏幕不会重新绘制)。

如果您想要为此更改制作动画,则有几种方法。您可以使用css转换或使用JavaScripts window.requestAnimationFrame()。但这超出了你的问题的范围;)。

PS:这个答案是基于我自己的观察以及我刚才读过的一些文章,所以如果你知道一些有关这种重绘行为的官方文件,请发表评论(我对此有点好奇)。