首页 文章

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

提问于
浏览
1

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

我为圆圈本身实现了非常平滑的运动,但 not for the text!

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

我尝试了什么:

Method 0 :只绘制圆圈,上面没有文字 . 动画很流畅 .
Problem :没问题,除了没有文字 .

Method 1 :使用canvas方法fillText()在圆圈顶部绘制文本 .
Problem :垂直移动时文本抖动 . 水平移动不会产生抖动 .

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

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

我的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();

2 回答

  • 2

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

    这是因为文本通常使用智能平滑算法进行渲染,并且画布无法访问此类子像素级别,因此它使模糊文本成为最佳 .

    当然,您可以尝试自己实现字体光栅化算法,SO用户Blindman67提供a good explanation along with a trying here,但它依赖于在移动目标上制作它的那么多参数(设备,UA,字体等)几乎是否定的-走 .

    因此,如果您需要在动画内容上进行完美的文字渲染, SVG is your friend . 但即使在SVG中,文本动画看起来也有点不稳定 .

    text{
      font: bold 16px Helvetica;
      fill: white;
      }
    
    <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>
    

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

    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();
    
    #canvas1 {
      border: 1px solid;
      width: 500px;
      height: 300px;
    }
    
    <canvas id="canvas1" width="500" height="300"></canvas>
    
  • 0

    这是因为:

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

    当您将要创建的画布的大小增加到 createHiDPICanvas(2000,2000, ...) 时,移动会平滑 . 现在你're creating a very small resolution canvas (30px by 30px), and the text looks jittery because it'在非常小的像素范围内移动 .

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

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

相关问题