我想为简单的针脚添加一个平滑的动画,上面有文字。这个引脚在画布周围缓慢移动,我需要流畅的动画。 所以我的图钉由背景(填充和阴影圆圈)和顶部的文字组成。
我为圆圈本身实现了非常平滑的移动,但不是为了文字!
方法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();
答案 0 :(得分:2)
您可以使用第三种方法a.k.a在屏幕外画布上实现文本的流畅移动 但是,实际上不可能在画布上实现平滑的文本渲染。
这是因为文本通常使用智能平滑算法在sub-pixel level进行渲染,并且画布无法访问此类子像素级别,因此它使模糊文本成为最佳。< / p>
当然,您可以尝试自己实现字体栅格化算法,SO用户Blindman67提供a good explanation along with a trying here,但它依赖于如此多的参数(设备,UA,字体等)使其成为现实移动目标几乎是不可行的。
因此,如果您需要完美的文字渲染,在动画内容上, SVG是您的朋友。但即使在SVG中,文本动画看起来也有点不稳定。
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;
否则,对于画布,您可以获得的最佳效果是将画布的大小加倍,并使用屏幕外的画布方法使用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();
&#13;
#canvas1 {
border: 1px solid;
width: 500px;
height: 300px;
}
&#13;
<canvas id="canvas1" width="500" height="300"></canvas>
&#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>