在画布上拖动文本



我在画布中发现了setLineDashstrokeText混合的有趣效果

ctx = document.querySelector("canvas").getContext("2d")
ctx.font = "110px Arial";
i = speed = 3
function loop() {
ctx.clearRect(0, 0, 600, 160)
ctx.beginPath()
ctx.setLineDash([i, 600]);
ctx.strokeText("WORLD ふ", 10, 100);
i += speed
if (i > 600 || i < 2)
speed *= -1
}
setInterval(loop, 50)
<canvas id="c" width=600 height=160></canvas>

正如您所看到的,在这个片段中,WO需要更长的绘制时间
是否有任何属性可以检索字母最长行的像素数(长度(?

您可以在每个屏幕外绘制字符,并"计数"像素的出现次数(非零值(:

function getAmount (char, { width, height, font, color }) {
// create temporary offscreen canvas
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
// draw the character
const ctx = canvas.getContext("2d")
ctx.font = font
ctx.strokeText(char, 0, 90)
// get the pixels data
const imageData = ctx.getImageData(0, 0, width, height)
let sum = 0
imageData.data.forEach(point => {
if (point > 0) sum++
})
return sum
}
const width = 90
const height = 90
const font = "90px Arial"
getAmount('W', { font, width, height }) // 940
getAmount('O', { font, width, height }) // 660
getAmount('R', { font, width, height }) // 673
getAmount('L', { font, width, height }) // 296
getAmount('D', { font, width, height }) // 613

您可以大致使用这些值来加权速度并单独绘制每个字符,但请记住,您还必须管理位置等。此外,这只检测任何非零值。如果使用渐变进行笔划,则必须检测渐变范围内的图像数据。

编辑:

既然找不到真相的来源,我们可以使用另一个技巧:

查找创建具有与全虚线字符相同像素量的屏幕外图像的i的数目。

/**
* draws a stroked text by given params to a context
**/
function draw (char, ctx, minValue, maxValue) {
ctx.clearRect(0, 0, 600, 160)
ctx.beginPath()
if (minValue && maxValue) {
ctx.setLineDash([minValue, maxValue])
}
ctx.strokeText(char, 10, 100);
}
/**
* Returns the amount of pixels for a given character
*/
const offscreenCanvas = document.createElement('canvas')
function getAmount (char, { value, max,  width, height, font }) {
// draw offscreen, then detect border pixels
offscreenCanvas.width = width
offscreenCanvas.height = height
// draw the character
const ctx = offscreenCanvas.getContext("2d")
ctx.font = font
draw(char, ctx, value, max)
// get the pixels data
const imageData = ctx.getImageData(0, 0, width, height)
let sum = 0
imageData.data.forEach(point => {
if (point > 0) sum++
})
return sum
}
/**
* Returns the number of iterations required to complete a character
**/
function getIterations (char, { font, width, height }) {
// get amount when word is fully drawn
const fullAmount = getAmount(char, { value: undefined, max: undefined, width, height, font })
let iterations = 1
let amount = 0
do {
amount = getAmount(char, { value: iterations, max: 1000, width, height, font })
iterations++
} while ((amount - fullAmount < -3) && iterations < 2000);
return iterations
}

从这里我们可以确定setLineDash参数的i值:

const font = "110px Arial";
const width = 110
const height = 110
const check = char => {
const amount = getIterations(char, { font, width, height })
console.log(char, amount)
}

check('W')  // 620
check('O')  // 243
check('R')  // 331
check('L')  // 248
check('D')  // 248
check('ふ') // 185

使用这些值,您可以创建一个相对的speed参数,该参数允许您同时完成笔划。

请注意,这种方法是超级贪婪的,并不是真正的性能优化,而是概念验证。

这是@Jankapunkt概念验证的优化版本:

function letterLen(text, font) {
const oc = document.createElement('canvas');
oc.width = oc.height = 200;
const octx = oc.getContext("2d");
octx.globalCompositeOperation = 'copy';
octx.imageSmoothingEnabled = false;
octx.font = font;
function pixels(char) {
octx.strokeText(char, 20, oc.height - 20);
const id = octx.getImageData(0, 0, oc.width, oc.height)
return id.data.reduce((a, c) => c ? a + 1 : a, 0)
}
function length(char) {
octx.setLineDash([]);
const full = pixels(char)
if (full == 0) return 0
let max = full
let min = 0
do {
let iter = Math.round((min + max) / 2)
octx.setLineDash([iter, 800]);
if (pixels(char) > full - 10)
max = iter
else
min = iter
} while (min + 1 < max)
return max
}
const strArray = text.split('');
return strArray.map(s => length(s));
}
ctx = document.getElementById("c").getContext("2d")
ctx.font = "110px Arial";
i = delta = 3
const world = "WORLDふ"
const wlen = letterLen(world, ctx.font)
const minlen = Math.min(...wlen)
function loop() {
ctx.clearRect(0, 0, 900, 600)
const letters = world.split('');
let ix = 10
for (j = 0; j < letters.length; j++) {
ctx.beginPath()
ctx.setLineDash([i * wlen[j] / minlen, 800]);
ctx.strokeText(letters[j], ix, 120);
ix += ctx.measureText(letters[j]).width
}
i += delta
if (i > minlen + 10 || i < 2)
delta *= -1
}
setInterval(loop, 50)
<canvas id="c" width=600 height=160></canvas>

正如我们现在所看到的,所有字母都在同一时间完成绘制

最新更新