【问题标题】:canvas to svg translate with animation画布到 svg 用动画翻译
【发布时间】:2015-11-12 09:34:15
【问题描述】:

我一直在使用JavaScript library 制作动画天气符号。我现在正在制作the cloud symbol 的 SVG 版本,想知道哪种方法可能是最好的。

所以我制作了云的轨迹/路径,静态 (jsFiddle) 和 basic rotation isn't the right effect,因为原始云 (in the js canvas animation) 基本上有 5 条曲线,它们在转动时会膨胀和收缩。

我想到的可能方法:

  • 制作 5 个圆圈,使用破折号仅显示所需的弧线,然后对圆圈进行动画处理和扩展/收缩

  • 制作 5 个 sub-paths 并以某种方式动画扩展和收缩它们

  • 使用 5 animateMotion 动画

这有更好的逻辑吗?任何关于如何认为这种逻辑的建议都会很棒。

和上面的 jsFiddle 一样的例子,使用 SE 应用:

var skycons = new Skycons({
    "color": "black"
});
var canvas = document.querySelectorAll('canvas');
[].forEach.call(canvas, function (el) {
    skycons.add(el, el.dataset.icon);
});

skycons.play();
<script src="https://rawgit.com/darkskyapp/skycons/master/skycons.js"></script>
<div class="fe_forecast">
    <div class="fe_currently">
        <canvas id="fe_current_icon" data-icon="cloudy" width="160" height="160" style="width:120px; height:120px"></canvas>
    </div>
</div>

【问题讨论】:

  • 我不认为&lt;animateMotion&gt; 对此有帮助,这里的动画是你的观点。您可以尝试使用&lt;animate attributeName="points"&gt; 并将from 设置为您的起始d 路径,并将to 设置为您从画布代码中获得的最后一个路径。或者,由于 svg 和 canvas 的路径绘制命令非常相似,您应该能够调整代码来为您的 svg 路径设置动画。
  • @Kaiido StueyTheScout 尝试了这个建议。这就是你的想法吗?

标签: javascript svg html5-canvas


【解决方案1】:

我被你提供的 svg 代码误导了:

我首先认为您的画布代码正在绘制一个 Path2d,使用 moveToarcToquadraticCurveTo 方法,这些方法很容易调整以便与您的 svg 的路径 d 属性进行交互,因为canvas 的路径命令与 SVG 的几乎相同。

但是,我猜你是通过像 InkScape 或 Illustrator 这样的软件将位图转换为矢量来获得当前的 svg 代码的。

实际上,Skycons 代码所做的是它绘制了 5 个圆圈,同时执行了一个复合操作,因此只有不重叠的笔划是可见的。

在这里我修改了代码,所以它变得很明显:

(function(global) {
  "use strict";

      var requestInterval = function(fn, delay) {
        var handle = {value: null};
        function loop() {
          handle.value = requestAnimationFrame(loop);
          fn();
        }
        loop();
        return handle;
      },
      cancelInterval = function(handle) {
        cancelAnimationFrame(handle.value);
      };

  var KEYFRAME = 500,
      STROKE = 0.08,
      TAU = 2.0 * Math.PI,
      TWO_OVER_SQRT_2 = 2.0 / Math.sqrt(2);

  function circle(ctx, x, y, r) {
    ctx.beginPath();
    ctx.arc(x, y, r, 0, TAU, false);
    ctx.stroke();
  }

  function puff(ctx, t, cx, cy, rx, ry, rmin, rmax) {
    var c = Math.cos(t * TAU),
        s = Math.sin(t * TAU);

    rmax -= rmin;

    circle(
      ctx,
      cx - s * rx,
      cy + c * ry + rmax * 0.5,
      rmin + (1 - c * 0.5) * rmax
    );
  }

  function puffs(ctx, t, cx, cy, rx, ry, rmin, rmax) {
    var i;
    for(i = 5; i--; )
      puff(ctx, t + i / 5, cx, cy, rx, ry, rmin, rmax);
  }

  function cloud(ctx, t, cx, cy, cw, s, color) {
    t /= 30000;

    var a = cw * 0.21,
        b = cw * 0.12,
        c = cw * 0.24,
        d = cw * 0.28;

    puffs(ctx, t, cx, cy, a, b, c, d);
    puffs(ctx, t, cx, cy, a, b, c - s, d - s);

  }

  var Skycons = function(opts) {
        this.list        = [];
        this.interval    = null;
        this.color       = opts && opts.color ? opts.color : "black";
        this.resizeClear = !!(opts && opts.resizeClear);
      };

  Skycons.CLOUDY = function(ctx, t, color) {
    var w = ctx.canvas.width,
        h = ctx.canvas.height,
        s = Math.min(w, h);
    cloud(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
  };


  Skycons.prototype = {
    _determineDrawingFunction: function(draw) {
      if(typeof draw === "string")
        draw = Skycons[draw.toUpperCase().replace(/-/g, "_")] || null;

      return draw;
    },
    add: function(el, draw) {
      var obj;

      if(typeof el === "string")
        el = document.getElementById(el);

      // Does nothing if canvas name doesn't exists
      if(el === null)
        return;

      draw = this._determineDrawingFunction(draw);

      // Does nothing if the draw function isn't actually a function
      if(typeof draw !== "function")
        return;

      obj = {
        element: el,
        context: el.getContext("2d"),
        drawing: draw
      };

      this.list.push(obj);
      this.draw(obj, KEYFRAME);
    },
    set: function(el, draw) {
      var i;

      if(typeof el === "string")
        el = document.getElementById(el);

      for(i = this.list.length; i--; )
        if(this.list[i].element === el) {
          this.list[i].drawing = this._determineDrawingFunction(draw);
          this.draw(this.list[i], KEYFRAME);
          return;
        }

      this.add(el, draw);
    },
    remove: function(el) {
      var i;

      if(typeof el === "string")
        el = document.getElementById(el);

      for(i = this.list.length; i--; )
        if(this.list[i].element === el) {
          this.list.splice(i, 1);
          return;
        }
    },
    draw: function(obj, time) {
      var canvas = obj.context.canvas;

      if(this.resizeClear)
        canvas.width = canvas.width;

      else
        obj.context.clearRect(0, 0, canvas.width, canvas.height);

      obj.drawing(obj.context, time, this.color);
    },
    play: function() {
      var self = this;

      this.pause();
      this.interval = requestInterval(function() {
        var now = Date.now(),
            i;

        for(i = self.list.length; i--; )
          self.draw(self.list[i], now);
      }, 1000 / 60);
    },
    pause: function() {
      var i;
      if(this.interval) {
        cancelInterval(this.interval);
        this.interval = null;
      }
    }
  };

  global.Skycons = Skycons;
}(this));
var skycons = new Skycons({
    "color": "black"
});
var canvas = document.querySelectorAll('canvas');
[].forEach.call(canvas, function (el) {
    skycons.add(el, el.dataset.icon);
});

skycons.play();
&lt;canvas id="fe_current_icon" data-icon="cloudy" width="160" height="160" style="width:120px; height:120px"&gt;&lt;/canvas&gt;

要重现相同的动画,您确实必须为您的圈子创建一个&lt;animationPath&gt;,然后分别设置它们的keyPointskeyTimes
浏览器仍然不支持svg's compositing operations,因此您必须使用&lt;clipPath&gt; 并声明两次您的圈子动画。

这是一个例子:

<svg version="1.1" id="svg" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="-30 -30 160 160" width=200 height=200>
  <defs>
    <style>
      circle {stroke: #000; stroke-width: 7px; fill: white;}
    </style>

    <path id="mainmotion" d="M15.5-3.9c13.2,0,23.9,7.4,23.9,16.5S28.7,29.2,15.5,29.2S-8.4,21.8-8.4,12.7S2.3-3.9,15.5-3.9"/>
	
	<g id="g">
	<circle id="c1" cx="37.7" cy="32.3" r="27.5">
        <animateMotion dur="6s" repeatCount="indefinite" calcMode="linear" keyPoints="0; 0.2; 0.4; 0.6; 0.8; 1" keyTimes="0; 0.2; 0.4; 0.6; 0.8; 1">
          <mpath xlink:href="#mainmotion" />
        </animateMotion>
      </circle>
      <circle id="c2" cx="37.7" cy="32.3" r="27.5">
        <animateMotion dur="6s" repeatCount="indefinite" calcMode="linear" keyPoints="0.2; 0.4; 0.6; 0.8; 1; 0; 0.2" keyTimes="0; 0.2; 0.4; 0.6; 0.8; 0.8; 1">
          <mpath xlink:href="#mainmotion" />
        </animateMotion>
      </circle>
      <circle id="c3" cx="37.7" cy="32.3" r="27.5">
        <animateMotion dur="6s" repeatCount="indefinite" calcMode="linear" keyPoints="0.4; 0.6; 0.8; 1; 0; 0.2; 0.4" keyTimes="0; 0.2; 0.4; 0.6; 0.6; 0.8; 1">
          <mpath xlink:href="#mainmotion" />
        </animateMotion>
      </circle>
      <circle id="c4" cx="37.7" cy="32.3" r="27.5">
        <animateMotion dur="6s" repeatCount="indefinite" calcMode="linear" keyPoints="0.6; 0.8; 1; 0; 0.2; 0.4; 0.6" keyTimes="0; 0.2; 0.4; 0.4; 0.6; 0.8; 1">
          <mpath xlink:href="#mainmotion" />
        </animateMotion>
      </circle>
      <circle id="c5" cx="37.7" cy="32.3" r="27.5">
        <animateMotion dur="6s" repeatCount="indefinite" calcMode="linear" keyPoints="0.8; 1; 0; 0.2; 0.4; 0.6; 0.8" keyTimes="0; 0.2; 0.2; 0.4; 0.6; 0.8; 1">
          <mpath xlink:href="#mainmotion" />
        </animateMotion>
      </circle>
    </g>
    <clipPath id="clip">
		<use xlink:href="#c1"/>
		<use xlink:href="#c2"/>
		<use xlink:href="#c3"/>
		<use xlink:href="#c4"/>
		<use xlink:href="#c5"/>
    </clipPath>
  </defs>
	<use xlink:href="#g"/>
	<rect x=0 y=0 width=160 height=160 clip-path="url(#clip)" fill="white" />
</svg>

【讨论】:

  • 非常有趣!感谢您对此进行额外的了解。将检查您的逻辑并尝试更进一步。
  • @Sergio,实际上我在第一个版本中做了一些愚蠢的事情。你可以只有一个animationPath,并设置每个圈子的keyPointskeyTimes属性达到同样的效果...
  • 好的,会检查的。你deom现在是statis,但会跟进这个想法。
  • 是的,Chrome 似乎不喜欢 calcMode='spline' 选项...将其更改为线性,它似乎解决了问题
  • 再次感谢您的反馈。看起来很棒!
猜你喜欢
  • 2016-08-09
  • 2011-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-28
  • 2012-04-13
  • 2014-10-03
  • 2013-08-14
相关资源
最近更新 更多