HTML5画布流畅的文字动画动画不可能吗?

时间:2017-04-21 14:53:29

标签: javascript html5 animation canvas html5-canvas

我想为简单的针脚添加一个平滑的动画,上面有文字。这个引脚在画布周围缓慢移动,我需要流畅的动画。 所以我的图钉由背景(填充和阴影圆圈)和顶部的文字组成。

我为圆圈本身实现了非常平滑的移动,但不是为了文字!

问:是否可以在HTML5画布中实现流畅的文本移动以及如何实现?

我尝试了什么:

方法0 :只绘制圆圈​​,上面没有文字。动画很流畅。
问题:完全没问题,除了没有文字。

方法1 :使用canvas方法 fillText()在圆圈顶部绘制文字。
问题:文字抖动在垂直移动时。水平移动不会产生抖动。

方法2 :在屏幕外画布上绘制文字,将画布复制为圆形顶部的图像。创建屏幕外画布并在其上绘制大小比原始大两倍的文本,然后在复制到屏幕画布时缩小。这将锐化文本。
问题:文字清晰但呈波浪状,并且在移动过程中会出现一些闪烁。

Method3 :在屏幕外画布上绘制文字,将画布复制为圆形顶部的图像。创建屏幕外画布并在那里绘制文本。画布和文本的大小与屏幕上的大小相同。
问题:运动足够平稳,但文字模糊,失焦。

我的JSFIDDLE:Canvas text jitter animation

我的Javascript代码:

var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");

ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "bold 16px Helvetica";
ctx.shadowOffsetX = ctx.shadowOffsetY = 2;
ctx.shadowBlur = 6;

var bgColor="blue";
var textColor="white";
var shadowColor="rgba(0, 0, 0, 0.4)";
var radius=15;

//Draw empty plate for the pin, no text on top
//Problem: NONE, movements are smooth
function drawPinPlate(x, y)
{
  var oldShadow = ctx.shadowColor;
  ctx.shadowColor = shadowColor;
  ctx.fillStyle = bgColor;
    ctx.beginPath();
  ctx.arc(x, y, radius, 0, 2*Math.PI);
  ctx.fill();
  ctx.shadowColor = oldShadow;
}

//method 1: Draw pin with text directly. 
//Draw text using canvas direct text rendering.
//Problem: Text vertical jittering while animating movement
function drawPin1(x, y, name)
{
    drawPinPlate(x, y);
  ctx.fillStyle = textColor;
  ctx.fillText(name, x, y);
}

//method 2: Draw pin with text using offscreen image with resize
//Draw text using text pre-rendered to offscreen canvas.
//Offscreen canvas is twice large than the original and we do resize (shrink) to the original one
//Problem: Text is sharp but some flickering appears during image movement
function drawPin2(x, y, name)
{
    drawPinPlate(x, y);
  ctx.drawImage(offImage1, x - radius, y - radius, radius*2, radius*2);
}

//method 2: Draw pin with text using offscreen image
//Draw text using text pre-rendered to offscreen canvas.
//Offscreen canvas is the same size as the original.
//Problem: Text is looking fuzzy, blurry
function drawPin3(x, y, name)
{
    drawPinPlate(x, y);
  ctx.drawImage(offImage2, x - radius, y - radius);
}


var PIXEL_RATIO = (function ()
{
    var ctx = document.createElement("canvas").getContext("2d"),
        dpr = window.devicePixelRatio || 1,
        bsr = ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio || 1;

    return dpr / bsr;
})();

//create offscreen canvas
createHiDPICanvas = function(w, h, ratio)
{
    if (!ratio) { ratio = PIXEL_RATIO; }
    var can = document.createElement("canvas");
    can.width = w * ratio;
    can.height = h * ratio;
    can.style.width = w + "px";
    can.style.height = h + "px";
    can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
    return can;
}

//create offscreen canvas with text, size of the canvas is twice larger than the original.
function createPin1(name)
{
    var cnv = createHiDPICanvas(radius*2, radius*2, 2);
  var ctx = cnv.getContext("2d");

    ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.font = "bold 16px Helvetica";

  ctx.fillStyle = textColor;
  ctx.fillText(name, radius, radius);

  return cnv;
}

//create offscreen canvas with text. Text becomes very blurry.
function createPin2(name)
{
    var cnv = createHiDPICanvas(radius*2, radius*2, 1);
  var ctx = cnv.getContext("2d");

    ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.font = "bold 16px Helvetica";

  ctx.fillStyle = textColor;
  ctx.fillText(name, radius, radius);

  return cnv;
}

var offImage1, offImage2;

offImage1 = createPin1("AB");
offImage2 = createPin2("AB");


var startTime;
var speed = 180;

//render one frame
function render(deltaTime)
{
    var x = (deltaTime / speed / 2) %100;
  var y = (deltaTime / speed) % 200;

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  for(var i = 0; i<4; i++)
  {
    ctx.fillText(i, 20 + x + i * 50, 15 + y);
  }

  drawPinPlate(20 + x, 40 + y);
  drawPin1(70 + x, 40 + y, "AB");
  drawPin2(120 + x, 40 + y, "AB");
  drawPin3(170 + x, 40 + y, "AB");
}

//Animation loop
function animate()
{
    requestAnimationFrame(animate);
  render(Date.now() - startTime);
}

//alert("You screen pixel ratio = " + PIXEL_RATIO);

startTime = Date.now();
animate();

3 个答案:

答案 0 :(得分:2)

您可以使用第三种方法a.k.a在屏幕外画布上实现文本的流畅移动 但是,实际上不可能在画布上实现平滑的文本渲染。

这是因为文本通常使用智能平滑算法在sub-pixel level进行渲染,并且画布无法访问此类子像素级别,因此它使模糊文本成为最佳。< / p>

当然,您可以尝试自己实现字体栅格化算法,SO用户Blindman67提供a good explanation along with a trying here,但它依赖于如此多的参数(设备,UA,字体等)使其成为现实移动目标几乎是不可行的。

因此,如果您需要完美的文字渲染,在动画内容上, SVG是您的朋友。但即使在SVG中,文本动画看起来也有点不稳定。

&#13;
&#13;
text{
  font: bold 16px Helvetica;
  fill: white;
  }
&#13;
<svg>
  <g id="group">
  <circle fill="blue" cx="15" cy="15" r="15"/>
  <text y="20" x="15" text-anchor="middle">AB
  </text>
    <animateTransform attributeName="transform"
                          attributeType="XML"
                          type="translate"
                          from="0 0"
                          to="80 150"
                          dur="20s"
                          repeatCount="indefinite"/>
  </g>
</svg>
&#13;
&#13;
&#13;

否则,对于画布,您可以获得的最佳效果是将画布的大小加倍,并使用屏幕外的画布方法使用CSS向后缩小它。

&#13;
&#13;
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");

var scale = devicePixelRatio *2;
canvas.width *= scale;
canvas.height *= scale;

function initTextsSprites(texts, radius, padding, bgColor, textColor, shadowColor) {
  // create an offscreen canvas which will be used as a spritesheet
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext('2d');
  radius *= scale;
  padding *= scale;
  
  var d = radius * 2;

  var cw = (d + (padding * 2));
  canvas.width = cw * texts.length;
  canvas.height = d * 2 + padding * 2;

  var topAlignText = 6 * scale; // just because I don't trust textBaseline
  var y;

  //  drawCircles
  ctx.fillStyle = bgColor;
  ctx.shadowOffsetX = ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 6;
  ctx.shadowColor = shadowColor;
  y = (radius * 2) + padding;
  ctx.beginPath();
  texts.forEach(function(t, i) {
    var cx = cw * i + padding * 2;
    ctx.moveTo(cx + radius, y)
    ctx.arc(cx, y, radius, 0, Math.PI * 2);
  })
  ctx.fill();

  // drawBlueTexts
  ctx.textAlign = "center";
  ctx.font = "bold "+(16 * scale)+"px Helvetica";
  ctx.shadowOffsetX = ctx.shadowOffsetY = ctx.shadowBlur = 0;
  y = padding + topAlignText;
  texts.forEach(function(txt, i) {
    var cx = cw * i + padding * 2;
    ctx.fillText(i, cx, y);
  });

  //  drawWhiteTexts
  ctx.fillStyle = 'white';
  var cy = (radius * 2) + padding + topAlignText;
  texts.forEach(function(txt, i) {
    var cx = cw * i + padding * 2;
    ctx.fillText(txt, cx, cy);
  });

  return function(index, x, y, w, h) {
    if (!w) {
      w = cw;
    }
    if (!h) {
      h = canvas.height;
    }
    // return an Array that we will able to apply on drawImage
    return [canvas,
      index * cw, 0, cw, canvas.height, // source
      x, y, w, h // destination
    ];
  };
}

var texts = ['', 'AA', 'AB', 'AC'];

var getTextSprite = initTextsSprites(texts, 15, 12, "blue", "white", "rgba(0, 0, 0, 0.4)");
// just to make them move independently
var objs = texts.map(function(txt) {
  return {
    x: Math.random() * canvas.width,
    y: Math.random() * canvas.height,
    speedX: Math.random() - .5,
    speedY: Math.random() - .5,
    update: function() {
      this.x += this.speedX;
      this.y += this.speedY;
      if (this.x < 0) {
        this.speedX *= -1;
        this.x = 0;
      }
      if (this.y < 0) {
        this.speedY *= -1;
        this.y = 0;
      }
      if (this.x > canvas.width) {
        this.speedX *= -1;
        this.x = canvas.width;
      }
      if (this.y > canvas.height) {
        this.speedY *= -1;
        this.y = canvas.height;
      }
    }
  }
});

function anim() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  objs.forEach(function(o, i) {
    o.update();
    ctx.drawImage.apply(ctx, getTextSprite(i, o.x, o.y));
  })
  requestAnimationFrame(anim);
}
anim();
&#13;
#canvas1 {
  border: 1px solid;
  width: 500px;
  height: 300px;
}
&#13;
<canvas id="canvas1" width="500" height="300"></canvas>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

因为这个原因:

var radius = 15;

var cnv = createHiDPICanvas(radius*2, radius*2, 2);
var ctx = cnv.getContext("2d");

当您将要创建的画布大小增加到createHiDPICanvas(2000,2000, ...)时,移动会平滑。现在你正在创建一个非常小的分辨率画布(30px×30px),文本看起来很紧张,因为它在非常小的像素范围内移动。

示例:https://jsfiddle.net/6xr4njLm/

对createHiDPICanvas的更长解释:How do I fix blurry text in my HTML5 canvas?

答案 2 :(得分:0)

经过一些测试,我发现在旋转1度后,问题似乎消失了,甚至还没有完全消失。但是,对于更长的文本,则可能需要分数。似乎可以改善到0.1度,任何较低的问题都会再次清晰可见。

在渲染示例之前,在示例中添加1度旋转:

https://jsfiddle.net/Fyrestar/c26u83vh/

const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );

let x = 0;

function render() {


  requestAnimationFrame( render );
  
  x += 0.01;
  
  const t =  0.2 + Math.abs( Math.sin( x ) ) * 0.8;
  
  ctx.clearRect( 0, 0, canvas.width, canvas.height );

  ctx.font = '40px Arial';
  
  ctx.save();
  ctx.scale( t, t );
  ctx.fillText( 'Hello World', 0 , 30 );
  ctx.restore();
  
  
  ctx.save();
  ctx.scale( t, t );
  ctx.rotate( Math.PI / 180 );
  ctx.fillText( 'Hello World', 0 , 60 );
  ctx.restore();

}

render();
<canvas id="canvas"></canvas>