Laz*_*vić 14 javascript canvas transformation
使用画布应用变换时,结果文本(显然)也会被转换.有没有办法阻止影响文本的某些转换,如反射?
例如,我设置了一个全局变换矩阵,因此Y轴指向上方,X轴指向右侧,(0, 0)点位于屏幕的中心(您对数学坐标系的期望).
但是,这也使文本颠倒了.
const size = 200;
const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);
const triangle = [
{x: -70, y: -70, label: 'A'},
{x: 70, y: -70, label: 'B'},
{x: 0, y: 70, label: 'C'},
];
// draw lines
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
triangle.forEach(v => ctx.fillText(v.label, v.x, v.y - 8));Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>Run Code Online (Sandbox Code Playgroud)
除了手动重置转换矩阵之外,是否有一种"智能"方式使文本处于"正确"方向?
我的解决方案是旋转画布然后绘制文本.
ctx.scale(1,-1); // rotate the canvas
triangle.forEach(v => {
ctx.fillText(v.label, v.x, -v.y + 25); // draw with a bit adapt position
});
Run Code Online (Sandbox Code Playgroud)
希望有帮助:)
const size = 200;
const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);
const triangle = [
{x: -70, y: -70, label: 'A'},
{x: 70, y: -70, label: 'B'},
{x: 0, y: 70, label: 'C'},
];
// draw lines
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
ctx.scale(1,-1);
triangle.forEach(v => {
ctx.fillText(v.label, v.x, -v.y + 25);
});Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>Run Code Online (Sandbox Code Playgroud)
为了建立Tai的答案,这很棒,你可能想要考虑以下几点:
const size = 200;
const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
// Create a custom fillText funciton that flips the canvas, draws the text, and then flips it back
ctx.fillText = function(text, x, y) {
this.save(); // Save the current canvas state
this.scale(1, -1); // Flip to draw the text
this.fillText.dummyCtx.fillText.call(this, text, x, -y); // Draw the text, invert y to get coordinate right
this.restore(); // Restore the initial canvas state
}
// Create a dummy canvas context to use as a source for the original fillText function
ctx.fillText.dummyCtx = document.createElement('canvas').getContext('2d');
ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);
const triangle = [
{x: -70, y: -70, label: 'A'},
{x: 70, y: -70, label: 'B'},
{x: 0, y: 70, label: 'C'},
];
// draw lines
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
// For this particular example, multiplying x and y by small factors >1 offsets the labels from the triangle vertices
triangle.forEach(v => ctx.fillText(v.label, 1.2*v.x, 1.1*v.y));Run Code Online (Sandbox Code Playgroud)
如果对于您的实际应用程序,您将在绘制非文本对象和绘制文本之间来回切换,并且不希望记得来回翻转画布.(这不是当前示例中的一个大问题,因为你绘制三角形然后绘制所有文本,所以你只需要一次翻转.但是如果你想到一个更复杂的不同应用程序,那可能是一个烦恼.)在上面的例子中,我用自定义方法替换了fillText方法,该方法翻转画布,绘制文本,然后再将其翻转回来,这样您就不必每次想要绘制文本时都手动完成.
结果:
如果您不喜欢覆盖默认值fillText,那么显然您可以创建一个具有新名称的方法; 这样你也可以避免创建虚拟上下文,只需this.fillText在自定义方法中使用.
编辑:上述方法也适用于任意缩放和翻译.scale(1, -1)简单地在x轴上反映画布:在此转换之后,(x,y)处的点现在将处于(x,-y).无论平移和缩放如何都是如此.如果您希望文本保持恒定大小而不管缩放,那么您只需通过缩放来缩放字体大小.例如:
<html>
<body>
<canvas id='canvas'></canvas>
</body>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
var framesPerSec = 100;
var msBetweenFrames = 1000/framesPerSec;
ctx.font = '12px Arial';
function getRandomCamera() {
return {x: ((Math.random() > 0.5) ? -1 : 1) * Math.random()*5,
y: ((Math.random() > 0.5) ? -1 : 1) * Math.random()*5+5,
zoom: Math.random()*20+0.1,
};
}
var camera = getRandomCamera();
moveCamera();
function moveCamera() {
var newCamera = getRandomCamera();
var transitionFrames = Math.random()*500+100;
var animationTime = transitionFrames*msBetweenFrames;
var cameraSteps = { x: (newCamera.x-camera.x)/transitionFrames,
y: (newCamera.y-camera.y)/transitionFrames,
zoom: (newCamera.zoom-camera.zoom)/transitionFrames };
for (var t=0; t<animationTime; t+=msBetweenFrames) {
window.setTimeout(updateCanvas, t);
}
window.setTimeout(moveCamera, animationTime);
function updateCanvas() {
camera.x += cameraSteps.x;
camera.y += cameraSteps.y;
camera.zoom += cameraSteps.zoom;
redrawCanvas();
}
}
ctx.drawText = function(text, x, y) {
this.save();
this.transform(1 / camera.zoom, 0, 0, -1 / camera.zoom, x, y);
this.fillText(text, 0, 0);
this.restore();
}
function redrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width / 2 - (camera.x * camera.zoom),
canvas.height / 2 + (camera.y * camera.zoom));
ctx.scale(camera.zoom, -camera.zoom);
for (var i = 0; i < 10; i++) {
ctx.beginPath();
ctx.arc(5, i * 2, .5, 0, 2 * Math.PI);
ctx.drawText(i, 7, i*2-0.5);
ctx.fill();
}
ctx.restore();
}
</script>
</html>Run Code Online (Sandbox Code Playgroud)
编辑:基于Blindman67建议的修改文本缩放方法.通过逐渐使相机运动逐步改进演示.