绘制和擦除圆弧 - 使用JavaScript或CSS进行圆弧动画

时间:2017-04-03 12:31:59

标签: javascript css canvas svg html5-canvas

我有一个案例,我想绘制3条弧线并擦除它们。

enter image description here

应逐步绘制第一个Arc CA,然后逐步删除它。然后应该绘制并擦除弧AB,然后弧BC也应该这样做。然后重复一遍。

我的方法:

使用canvas和JS:

我从画布开始,但抗锯齿在这里没有效果。所以我想可能是SVG会更好。

			var currentEndAngle = 0;
			var currentStartAngle = 0;
			var currentColor = 'black';
			var lineRadius = 300;
			var lineWidth = 5;

			setInterval(draw, 5);

			function draw() { 

			    var can = document.getElementById('canvas1'); // GET LE CANVAS
			    var canvas = document.getElementById("canvas1");
			    var context = canvas.getContext("2d");
			    var x = canvas.width / 2;
			    var y = canvas.height / 2;
			    var radius;
			    var width;

			    var startAngle = currentStartAngle * Math.PI;
			    var endAngle = (currentEndAngle) * Math.PI;
			    
			    currentStartAngle = currentEndAngle - 0.01;
			    currentEndAngle = currentEndAngle + 0.01;
			    
			    if (Math.floor(currentStartAngle / 2) % 2) {
			      currentColor = "white";
			      radius = lineRadius - 1;
			      width = lineWidth + 3;
			    } else {
			      currentColor = "black";
			      radius = lineRadius;
			      width = lineWidth;
			    }
			            
			    var counterClockwise = false;

			    context.beginPath();
			    context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
			    context.lineWidth = width;
			    // line color
			    context.strokeStyle = currentColor;
			    context.stroke();

			    /************************************************/
			}
            body {
				text-align: center;
                background: blue;
			} 
			#canvas1 {
				width: 500px;
				height: 500px;
				margin: 0 auto;
			} 
<canvas id="canvas1" width="700" height="700"></canvas>

使用SVG和CSS

SVG方法看起来更顺畅。但我不明白如何修改dasharray,dashoffset和圆的半径以获得3个弧形动画。

	circle {
	  fill: transparent;
	  stroke: black;
	  stroke-width: 2;
	  stroke-dasharray: 250;
	  stroke-dashoffset: 0;
	  animation: rotate 5s linear infinite;
	}

	@keyframes rotate {
	  0% {
	    stroke-dashoffset: 500;
	  }
	  100% {
	    stroke-dashoffset: 0;
	  }
	}
<svg height="400" width="400">
  <circle cx="100" cy="100" r="40" />
</svg>

那么,如果有人可以帮我扩展代码或指导如何从svg圈创建三个弧以及如何设置dasharray,dashoffset和radius?

如果您有更好的解决方案,那么上述2方法请告诉我。

我也曾尝试使用GSAP的drawingvg插件,我想这可能会更容易,但我不允许在我的项目中使用'drawnvg'插件。

3 个答案:

答案 0 :(得分:3)

你真的不想修改stroke-dashoffset,因为这只会在圆圈周围移动短划线。

您还必须修改破折号数组值,因此您可以通过设置破折号数组中的值来完成所有操作。

你的圆的半径为40,所以圆周为251.33。这意味着你的三个弧的长度都是83.78。

对于这三个阶段中的每个阶段,我们都会在&#34;&#34;破折号的一部分从0到83.78。然后我们再次缩减它,同时将先前的差距从83.78增加到167.55。这样尾巴就会被推到最后。

这适用于前两个步骤,但由于短划线模式在3点钟位置开始和结束(并且没有包围该点),我们必须进行尾推最后一步,在开始时使用一个额外的空虚线对。我们将该差距从0增加到83.78。

&#13;
&#13;
circle {
  fill: transparent;
  stroke: black;
  stroke-width: 2;
  animation: rotate 5s linear infinite;
}

@keyframes rotate {
  0%    { stroke-dasharray: 0 0      0     83.78   0     83.78   0     83.78; }
  16.7% { stroke-dasharray: 0 0      0     83.78  83.78   0      0     83.78; }
  33.3% { stroke-dasharray: 0 0      0    167.55   0      0      0     83.78; }
  50%   { stroke-dasharray: 0 0      0     83.78   0     83.78  83.78   0;    }
  66.6% { stroke-dasharray: 0 0      0     83.78   0    167.55   0      0;    }
  83.3% { stroke-dasharray: 0 0     83.78   0      0     83.78   0     83.78; }
  100%  { stroke-dasharray: 0 83.78  0      0      0     83.78   0     83.78; }
}
&#13;
<svg height="400" width="400">
  <circle cx="100" cy="100" r="40" />
</svg>
&#13;
&#13;
&#13;

答案 1 :(得分:2)

对于画布版本,如评论中所述,您的抗锯齿问题是您在相同的像素上反复重绘。

为避免这种情况,请每帧清除整个画布并重新绘制所有内容。

对于您要求的动画,您必须同时存储起始角度和结束角度。然后你会一个接一个地递增,当你超过分割大小阈值时进行切换。

这是一个带注释的代码片段,可以让我希望更清楚。

// settings
var divisions = 3;
var duration = 3000; // in ms
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = (canvas.width / 7) * 2;
context.lineWidth = 4;

// init
var currentSplit = 0;
var splitAngle = (Math.PI * 2) / divisions;
var splitTime = (duration / (divisions*2)); // how much time per split per end
var angles = [0,0]; // here we store both start and end angle
var current = 0;
var startTime = performance.now();
draw();

function draw(currentTime) {
  // first convert the elapsed time to an angle
  var timedAngle =  ((currentTime - startTime) / splitTime) * splitAngle;
  // set the current end to this timed angle + the current position on the circle
  angles[current] = timedAngle + (splitAngle * currentSplit);

  if (timedAngle >= splitAngle) {  // one split is done for this end
    // it should not go farther than the threshold
    angles[current] = (splitAngle * (currentSplit + 1));
    current = +(!current) // switch which end should move
    startTime = currentTime; // reset the timer

    if(!current){ // we go back to the start
      currentSplit = (currentSplit + 1) % divisions; // increment our split index
      }
  }

  if(angles[1] > Math.PI*2){ // we finished one complete revolution
    angles[0] = angles[1] = current = 0; // reset everything
    }
  
  // at every frame we clear everything
  context.clearRect(0, 0, canvas.width, canvas.height);
  // and redraw
  context.beginPath();
  context.arc(x, y, radius, angles[0], angles[1], true);
  context.stroke();

  requestAnimationFrame(draw); // loop at screen refresh rate
}
body {
  text-align: center;
}

#canvas1 {
  width: 250px;
  height: 150px;
}
<canvas id="canvas1" width="500" height="300"></canvas>

答案 2 :(得分:1)

Javascript扩展HTML

Canvas,(或CSS,HTML,SVG)与javascript相结合,总是胜过CSS,SVG,HTML,因为Javascript更具适应性。 HTML,CSS和SVG是声明性语言,而JavaScript是一种功能齐全的命令式语言,可以执行任何其他编程语言所能做的任何事情。

使用javascript添加到HTML,CSS,SVG功能,有效地声明这些语言的新行为。

一旦定义了Javascript功能,就可以忘记javascript,并根据需要使用HTML,CSS或SVG调用新行为。

在这种情况下,所有具有类名"segmentedProgress"的元素都将成为动画进度。您可以根据需要设置任意数量的属性来控制行为并将其添加到元素的数据属性中。

例如

<div class="segmentedProgress"></div>
<!-- showing defaults as above element will be setup -->
<div class="segmentedProgress" 
      data-angle-steps = 3      <!-- number of segments. (integers only) -->
      data-speed = 1000         <!-- Time per segment in ms -->
      data-easing = "1.2"       <!-- easing power -->
      data-line-width = "0.1"   <!-- as fraction of radius -->
      data-radial-size = "0.33" <!-- as fraction of shortest dimension -->
      data-color = "black"      <!-- colour of line -->
></div>

只要包含Javascript,就会在页面上自动显示正确配置的每个元素的进度。如果您的服务器设置可以识别页面内容依赖性,那么您需要做的就是将行为添加到页面,因为服务器将添加使其运行所需的内容。

javascript

实现并不需要太多的javascript。您将找到具有相应类名的所有元素,并将它们添加到进度项数组中。然后根据需要制作动画。

document.addEventListener("load", function(){
    var elements = [...document.body.querySelectorAll(".segmentedProgress")];
    if(elements.length === 0){  // exit if nothing found
        return;
    }
    // singleton to isolate from onload 
    (function(){
        const error = 0.01; // Math too perfect causes zero len arc to draw nothing. Error makes sure there is always some length in the drawn arc
        const items = [];  // array of progress items

        // each progress item defaults
        var defaults = {
            angleSteps : 3,  // number of segments. (integers only)
            speed : 1000,  // Time per segment in ms
            easing : 1.2, // easing power where 1 = no easing 2 = normal quadratic easing 1/2= inverse quadratic easing
            lineWidth : 0.1, // as fraction of radius
            radialSize : 0.33,// as fraction of shortest dimension
            color : "black",  // colour of line
            complete : false, // not used
            resize () { // resize the canvas and set size dependent vars
                this.bounds = this.element.getBoundingClientRect();
                this.w = this.canvas.width = this.bounds.width;
                this.h = this.canvas.height = this.bounds.height;
                this.canvas.style.top = (this.bounds.top + scrollY) + "px";  
                this.canvas.style.left = (this.bounds.left + scrollX) + "px";
                this.pos = { x : this.w / 2, y : this.h / 2}; // position of circle 
                this.radius = Math.min(this.w, this.h) * this.radialSize;    // radius of circle

                // set canvas state constants
                this.ctx.lineCap = "round";    
            },    
            update (time) { // updates and renders
                var segStart, segProgress, pp, ctx, ang;

                ctx = this.ctx; // alias to this.ctx

                // clear the canvas
                ctx.clearRect(0, 0, this.w, this.h);

                // get current selment angle
                ang = Math.PI * 2 / this.angleSteps, // Radians per segment

                // set the time at the correct speed
                time /= this.speed;

                // get the segment start position in radians
                segStart = Math.floor(time % this.angleSteps) * ang;

                // get the unit progress of this stage doubled for grow and shrink stages
                var segProgress = (time % 1) * 2;
                var pp = segProgress % 1;  // pp partial progress
                pp = (pp ** this.easing) / ((pp ** this.easing) + (1 - pp) ** this.easing); // add some easing

                ctx.beginPath();

                // first half of progress is growth
                if(segProgress <= 1){
                    ctx.arc(this.pos.x, this.pos.y, this.radius, segStart, segStart + pp * ang + error);
                }else{
                    // second half of progress is shrink
                    ctx.arc(this.pos.x, this.pos.y, this.radius, segStart + pp * ang - error, segStart + ang);
                }
                ctx.strokeStyle = this.color;
                ctx.lineWidth  = this.radius * this.lineWidth;
                ctx.stroke();   
            }
        }

        // create prgress item for each found element
        elements.forEach(element => {
            var pItem = {...defaults}; // progress item
            pItem.element = element;
            // get any element setting that overwrite the defaults
            Object.keys(defaults).forEach(key => {
                if(typeof defaults[key] !== "function"){
                    if(element.dataset[key] !== undefined){
                        pItem[key] = element.dataset[key];
                        if(! isNaN(element.dataset[key])){
                            pItem[key] = Number(pItem[key]);
                        }
                    }
                }
            });
            pItem.canvas = document.createElement("canvas");
            pItem.ctx = pItem.canvas.getContext("2d");
            pItem.canvas.style.position = "absolute";
            pItem.resize();
            items.push(pItem);
            element.appendChild(pItem.canvas);
        });
        elements.length = 0; // let go of elements

        // change size on resize
        window.addEventListener("resize", () =>{
            items.forEach(pItem => pItem.resize());
        });

        // start the animation
        requestAnimationFrame(update);    

        // main update loop
        function update (time) {
            items.forEach(pItem => {
                pItem.update(time);
            });
            requestAnimationFrame(update);                
        }
    }());
}());

作为演示

//document.addEventListener("load",()=>{
;(function(){
    var elements = [...document.body.querySelectorAll(".segmentedProgress")];
    if (elements.length === 0) { return }
    (function () {
        const error = 0.001; // Math too perfect causes zero len arc to draw nothing. Error makes sure there is always some length in the drawn arc
        const items = [];  // array of progress items
        var defaults = {
            angleSteps : 3,  // number of segments. (integers only)
            speed : 1000,  // Time per segment in ms
            easing : 1.2, // easing power where 1 = no easing 2 = normal quadratic easing 1/2= inverse quadratic easing
            lineWidth : 0.1, // as fraction of radius
            radialSize : 0.33,// as fraction of shortest dimension
            color : "black",  // colour of line
            complete : false, // not used
            resize () { // resize the canvas and set size dependent vars
                this.bounds = this.element.getBoundingClientRect();
                this.w = this.canvas.width = this.bounds.width;
                this.h = this.canvas.height = this.bounds.height;
                this.canvas.style.top = (this.bounds.top + scrollY) + "px";  
                this.canvas.style.left = (this.bounds.left + scrollX) + "px";
                this.pos = { x : this.w / 2, y : this.h / 2}; // position of circle 
                this.radius = Math.min(this.w, this.h) * this.radialSize;    // radius of circle
                this.ctx.lineCap = "round";    
            },    
            update (time) { // updates and renders
                var segStart, segProgress, pp, ctx, ang;
                ctx = this.ctx; // alias to this.ctx
                ctx.clearRect(0, 0, this.w, this.h);
                ang = Math.PI * 2 / this.angleSteps, // Radians per segment
                time /= this.speed;
                segStart = Math.floor(time % this.angleSteps) * ang;
                var segProgress = (time % 1) * 2;
                var pp = segProgress % 1;  // pp partial progress
                // babel can not handle the following line even though most
                // browsers can
                // pp = (pp ** this.easing) / ((pp ** this.easing) + (1 - pp) ** this.easing); // add some easing
                // to cover babel error
                pp = Math.pow(pp,this.easing) / (Math.pow(pp,this.easing) + Math.pow(1 - pp, this.easing)); // add some easing
                ctx.beginPath();
                if(segProgress <= 1){
                    ctx.arc(this.pos.x, this.pos.y, this.radius, segStart, segStart + pp * ang + error);
                }else{
                    ctx.arc(this.pos.x, this.pos.y, this.radius, segStart + pp * ang - error, segStart + ang);
                }
                ctx.strokeStyle = this.color;
                ctx.lineWidth  = this.radius * this.lineWidth;
                ctx.stroke();   
            }
        }
        elements.forEach(element => {
            var pItem = {...defaults}; // progress item
            pItem.element = element;
            Object.keys(defaults).forEach(key => {
                if(typeof defaults[key] !== "function"){
                    if(element.dataset[key] !== undefined){
                        pItem[key] = element.dataset[key];
                        if(! isNaN(element.dataset[key])){
                            pItem[key] = Number(pItem[key]);
                        }
                    }
                }
            });
            pItem.canvas = document.createElement("canvas");
            pItem.ctx = pItem.canvas.getContext("2d");
            pItem.canvas.style.position = "absolute";
            pItem.resize();
            items.push(pItem);
            element.appendChild(pItem.canvas);
        });
        elements.length = 0; 
        window.addEventListener("resize", () =>{ items.forEach(pItem => pItem.resize()) });
        requestAnimationFrame(update);    
        function update (time) {
            items.forEach(pItem => { pItem.update(time) });
            requestAnimationFrame(update);                
        }
    }());
}());
.segmentedProgress {
  width : 100px;
  height : 100px;
}
.big {
  width : 200px;
  height : 200px;
}
.large {
  width : 512px;
  height : 512px;
  background : #4AF;
}
4 segment fast.
<div class="segmentedProgress" data-color="red" data-speed ="250" data-line-width="0.3" data-angle-steps=4   ></div>
Default Progress
<div class="segmentedProgress" ></div>
Big progress
<div class="big segmentedProgress" data-color="blue" data-speed ="2500" data-line-width="0.3" data-angle-steps=2  ></div>
60 Seconds two overlap

<div class="large segmentedProgress" data-color="white" data-speed ="1000" data-line-width="0.02" data-angle-steps=60  >
<div class="large segmentedProgress" data-color="white" data-speed ="1000" data-line-width="0.02" data-angle-steps=2  data-radial-size = "0.34">
</div>