HTML5画布流畅的文字动画动画不可能? [英] HTML5 canvas smooth text movement animation impossible?

查看:83
本文介绍了HTML5画布流畅的文字动画动画不可能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



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



我尝试了什么:



方法0 :只需绘制圆圈,上面没有文字。动画很流畅。

问题:完全没问题,除了没有文字。



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



方法2 :在屏幕外画布上绘制文本,将画布复制为圆形顶部的图像。创建屏幕外画布并在其上绘制大小比原始大两倍的文本,然后在复制到屏幕画布时缩小。这将锐化文本。

问题:文字清晰但呈波浪状,并且在移动过程中会出现一些闪烁。



方法3 :在屏幕外的画布上绘制文本,将画布复制为圆形顶部的图像。创建屏幕外画布并在那里绘制文本。画布和文本的大小与屏幕上的大小相同。

问题:移动非常流畅,但文字模糊,失焦。



我的JSFIDDLE:< a href =https://jsfiddle.net/NorthernCaptain/ry47bjvr/25/ =nofollow noreferrer>画布文字抖动动画



我的Javascript代码:

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

ctx.textAlign =center;
ctx.textBaseline =middle;
ctx.font =粗体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;

//为引脚绘制空盘,顶部没有文字
//问题:无,动作平滑
函数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;
}

//方法1:直接用文字绘制图钉。
//使用画布直接文本渲染绘制文本。
//问题:动画运动时文本垂直抖动
函数drawPin1(x,y,name)
{
drawPinPlate(x,y);
ctx.fillStyle = textColor;
ctx.fillText(name,x,y);
}

//方法2:使用带有调整大小的屏幕外图像绘制带文本的图钉
//使用预渲染到屏幕外画布的文本绘制文本。
//离屏画布是原版的两倍大,我们调整大小(缩小)到原来的一个
//问题:文字很清晰但在图像移动过程中出现一些闪烁
function drawPin2( x,y,name)
{
drawPinPlate(x,y);
ctx.drawImage(offImage1,x - radius,y - radius,radius * 2,radius * 2);
}

//方法2:使用屏幕外图像绘制带文本的图钉
//使用预渲染到屏幕外画布的文本绘制文本。
//屏幕外画布与原始画布大小相同。
//问题:文字模糊,模糊
函数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;
})();

//创建屏幕外画布
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);
返回可以;
}

//使用文本创建屏幕外画布,画布的大小是原始大小的两倍。
函数createPin1(name)
{
var cnv = createHiDPICanvas(radius * 2,radius * 2,2);
var ctx = cnv.getContext(2d);

ctx.textAlign =center;
ctx.textBaseline =middle;
ctx.font =粗体16px Helvetica;

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

返回cnv;
}

//使用文本创建屏幕外画布。文字变得非常模糊。
函数createPin2(name)
{
var cnv = createHiDPICanvas(radius * 2,radius * 2,1);
var ctx = cnv.getContext(2d);

ctx.textAlign =center;
ctx.textBaseline =middle;
ctx.font =粗体16px Helvetica;

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

返回cnv;
}

var offImage1,offImage2;

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


var startTime;
var speed = 180;

//渲染一帧
函数渲染(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);
}

//动画循环
函数animate()
{
requestAnimationFrame(animate);
render(Date.now() - startTime);
}

// alert(你屏幕像素比=+ PIXEL_RATIO);

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


解决方案

通过使用您的文本,您可以实现文本的流畅移动第三种方法也就是离屏画布。

但是,在画布上实现平滑的文本渲染并不是真的可行。



这是因为文本通常使用智能平滑算法进行渲染,位于子像素级别并且该画布无法访问此类子像素级别,因此它为我们留下了最模糊的文本。



当然,您可以尝试自己实现字体光栅化算法,SO用户Blindman67提供一个很好的解释以及尝试在这里,但它取决于在移动目标上制作它的那么多参数(设备,UA,字体等)几乎是不可行的。



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



  text {font:bold 16px Helvetica;填充:白色; }  

 < svg> < g id =group> < circle fill =bluecx =15cy =15r =15/> < text y =20x =15text-anchor =middle> AB< / text> < animateTransform attributeName =transformattributeType =XMLtype =translatefrom =0 0to =80 150dur =20srepeatCount =indefinite/> < / g>< / svg>  



否则,画布,你可以得到的最好的是将画布的大小加倍,并使用屏幕后的画布方法使用CSS向后缩小它。



< pre class =snippet-code-js lang-js prettyprint-override> var canvas = document.getElementById(canvas1); var ctx = canvas.getContext(2d); var scale = devicePixelRatio * 2; canvas.width * = scale; canvas.height * = scale; function initTextsSprites(text,radius,padding,bgColor,textColor,shadowColor){//创建一个将用作spritesheet var canvas =的offscreen画布=使用document.createElement( '画布'); var ctx = canvas.getContext('2d');半径* =比例;填充* =比例; var d = radius * 2; var cw =(d +(padding * 2)); canvas.width = cw * texts.length; canvas.height = d * 2 + padding * 2; var topAlignText = 6 * scale; //只因为我不信任textBaseline var y; // drawCircles ctx.fillStyle = bgColor; ctx.shadowOffsetX = ctx.shadowOffsetY = 2; ctx.shadowBlur = 6; ctx.shadowColor = shadowColor; y =(半径* 2)+填充; 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函数(index,x,y,w,h){if(!w){w = cw; } if(!h){h = canvas.height; } //返回一个我们将能够在drawImage上应用的数组返回[canvas,index * cw,0,cw,canvas.height,// source x,y,w,h // destination]; };} var texts = ['','AA','AB','AC']; var getTextSprite = initTextsSprites(文本,15,12,蓝色,白色,rgba(0,0,0) ,0.4)); //只是让它们独立移动变换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,ox,oy));})requestAnimationFrame(anim);} anim();

 #canvas1 {border:1px solid;宽度:500px;身高:300px;}  

 < canvas id =canvas1 width =500height =300>< / canvas>  


I'd like to have a smooth animation for simple pin with the text on it. This pin is moving slowly around the canvas and I need smooth animation. So my pin consists from the background (filled and shadowed circle) and a text on top of it.

I achieved very smooth movement for the circle itself, but not for the text!

Q: Is it possible to achieve smooth text movement in the HTML5 canvas and how?

What I tried:

Method 0: Just draw the circle, no text on it. Animation is smooth.
Problem: No problem at all, except there is no text.

Method 1: Draw text on top of the circle using canvas method fillText().
Problem: Text is jittering while moving vertically. Horizontal moving does not produce jittering.

Method 2: Draw text to the offscreen canvas, copy canvas as an image on top of the circle. Create offscreen canvas and draw the text on it with sizes twice bigger than the original and then shrink while copying to the screen canvas. This will sharpen the text.
Problem: Text is sharp, but wavy and there is some flickering appears during movement.

Method3: Draw text to the offscreen canvas, copy canvas as an image on top of the circle. Create offscreen canvas and draw the text there. Size of the canvas and the text is the same as on the screen.
Problem: Movement is smooth enough, but text is blurry, out of focus.

My JSFIDDLE: Canvas text jitter animation

My Javascript code:

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

解决方案

You can achieve smooth movement of text, by using your third method a.k.a the offscreen canvas.
However, it is not really possible to achieve smooth text rendering on canvas.

This is because texts are usually rendered with smart smoothing algorithms, at sub-pixel level and that canvas doesn't have access to such sub-pixel level, so it leaves us with blurry texts as a best.

Of course, you could try to implement a font-rasterization algo by yourself, SO user Blindman67 provided a good explanation along with a trying here, but it depends on so much parameters (which device, UA, font etc.) that making it on a moving target is almost a no-go.

So if you need perfect text rendering, on animated content, SVG is your friend. But even in SVG, text animation looks somewhat choppy.

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>

Otherwise, for canvas, the best you could get would be to double the size of your canvas, and scale it down with CSS after-ward, using the offscreen canvas method.

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>

这篇关于HTML5画布流畅的文字动画动画不可能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆