HTML5 Canvas Javascript - 如何使用 'translate3d' 制作带有瓷砖的可移动 canvas?
HTML5 Canvas Javascript - How to make a moveable canvas with tiles using 'translate3d'?
我在使用可移动的 canvas 时遇到问题,它随着 'player' 在地图上的移动而调整。由于绘制 600 个图块,每秒 60 次非常低效,我转而使用 translate3d
并且在玩家穿过完整的图块后仅使用 draw
- 但它一直出现故障并且移动不流畅。我该如何正确地实现这一目标?
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function moveMap(){
const sector = {
x: Math.round(-MAIN.position.x % tileSize),
y: Math.round(-MAIN.position.y % tileSize)
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
requestAnimationFrame(drawMap);
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id=canvas></canvas>
根据上面的评论,它表明您只想在现有代码上平滑地移动 canvas 并且不打算修改,您是否尝试过向 canvas
添加缓动过渡元素?
canvas { transition: all 1500ms cubic-bezier(0.250, 0.100, 0.250, 1.000); transition-timing-function: cubic-bezier(0.250, 0.100, 0.250, 1.000); /* ease (default) */ }
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function moveMap(){
const sector = {
x: Math.round(-MAIN.position.x % tileSize),
y: Math.round(-MAIN.position.y % tileSize)
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
requestAnimationFrame(drawMap);
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
canvas {
transition: all 1500ms cubic-bezier(0.250, 0.100, 0.250, 1.000); /* ease (default) */
transition-timing-function: cubic-bezier(0.250, 0.100, 0.250, 1.000); /* ease (default) */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id=canvas></canvas>
我个人不会移动 canvas 本身,但会移动其中的元素,方法是在方向上添加一个 row/column 并移除相反方向的方块。但是,这应该可以解决问题
提出的问题
发生了一些事情:
立即调用 drawMap
而不是使用 requestAnimationFrame
正如 ggorlen 在评论中提到的,在更新周期中多次使用 requestAnimationFrame
是一种不寻常的做法。当您使用 requestAnimationFrame
时,您将在下一帧更新时调用该函数,这意味着将有一个帧不会重新绘制地图,从而导致轻微的闪烁。相反,如果您立即调用它,它将重新绘制该帧的地图。此外,将所有绘制和更新合并到 requestAnimationFrame
的一次调用是个好主意,因为它可以更清楚地说明更新的顺序。
所以你应该把requestAnimationFrame(drawMap);
改成drawMap();
使用非整数求余数
模运算(即 %
运算符)通常适用于整数。在您有 MAIN.position.x % tileSize
的情况下,它会经常出现故障,因为 tileSize
不是整数 (200 / 6)。要使用非整数求余数,我们可以使用自定义函数:
function remainder(a, b) {
return a - Math.floor(a / b) * b;
}
并用我们的新函数替换模运算实例(例如,将 MAIN.position.x % tileSize
更改为 remainder(MAIN.position.x, tileSize)
)
Math.round
对比 Math.floor
最后,您可能想使用 Math.floor
而不是 Math.round
,因为 Math.round
returns 0,两者的范围都在 (-1, 0) 和 ( 0, 1), 而 Math.floor
returns -1, 和 0.
使用容器和 css 隐藏 canvas
的移动部分
您可能希望使用包含 div 和对应的 css 来隐藏正在重绘的 canvas 的边缘:
在HTML:
<div class="container">
<canvas id=canvas></canvas>
</div>
在CSS:
.container {
width: 560px;
height: 160px;
overflow: hidden;
}
一起
整体看起来像这样:
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function remainder(a, b) {
return a - Math.floor(a / b) * b;
}
function moveMap(){
const sector = {
x: Math.floor(-remainder(MAIN.position.x, tileSize)),
y: Math.floor(-remainder(MAIN.position.y, tileSize))
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
drawMap();
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
.container {
width: 560px;
height: 160px;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<canvas id=canvas></canvas>
</div>
我在使用可移动的 canvas 时遇到问题,它随着 'player' 在地图上的移动而调整。由于绘制 600 个图块,每秒 60 次非常低效,我转而使用 translate3d
并且在玩家穿过完整的图块后仅使用 draw
- 但它一直出现故障并且移动不流畅。我该如何正确地实现这一目标?
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function moveMap(){
const sector = {
x: Math.round(-MAIN.position.x % tileSize),
y: Math.round(-MAIN.position.y % tileSize)
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
requestAnimationFrame(drawMap);
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id=canvas></canvas>
根据上面的评论,它表明您只想在现有代码上平滑地移动 canvas 并且不打算修改,您是否尝试过向 canvas
添加缓动过渡元素?
canvas { transition: all 1500ms cubic-bezier(0.250, 0.100, 0.250, 1.000); transition-timing-function: cubic-bezier(0.250, 0.100, 0.250, 1.000); /* ease (default) */ }
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function moveMap(){
const sector = {
x: Math.round(-MAIN.position.x % tileSize),
y: Math.round(-MAIN.position.y % tileSize)
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
requestAnimationFrame(drawMap);
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
canvas {
transition: all 1500ms cubic-bezier(0.250, 0.100, 0.250, 1.000); /* ease (default) */
transition-timing-function: cubic-bezier(0.250, 0.100, 0.250, 1.000); /* ease (default) */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id=canvas></canvas>
我个人不会移动 canvas 本身,但会移动其中的元素,方法是在方向上添加一个 row/column 并移除相反方向的方块。但是,这应该可以解决问题
提出的问题发生了一些事情:
立即调用 drawMap
而不是使用 requestAnimationFrame
正如 ggorlen 在评论中提到的,在更新周期中多次使用 requestAnimationFrame
是一种不寻常的做法。当您使用 requestAnimationFrame
时,您将在下一帧更新时调用该函数,这意味着将有一个帧不会重新绘制地图,从而导致轻微的闪烁。相反,如果您立即调用它,它将重新绘制该帧的地图。此外,将所有绘制和更新合并到 requestAnimationFrame
的一次调用是个好主意,因为它可以更清楚地说明更新的顺序。
所以你应该把requestAnimationFrame(drawMap);
改成drawMap();
使用非整数求余数
模运算(即 %
运算符)通常适用于整数。在您有 MAIN.position.x % tileSize
的情况下,它会经常出现故障,因为 tileSize
不是整数 (200 / 6)。要使用非整数求余数,我们可以使用自定义函数:
function remainder(a, b) {
return a - Math.floor(a / b) * b;
}
并用我们的新函数替换模运算实例(例如,将 MAIN.position.x % tileSize
更改为 remainder(MAIN.position.x, tileSize)
)
Math.round
对比 Math.floor
最后,您可能想使用 Math.floor
而不是 Math.round
,因为 Math.round
returns 0,两者的范围都在 (-1, 0) 和 ( 0, 1), 而 Math.floor
returns -1, 和 0.
使用容器和 css 隐藏 canvas
的移动部分您可能希望使用包含 div 和对应的 css 来隐藏正在重绘的 canvas 的边缘:
在HTML:
<div class="container">
<canvas id=canvas></canvas>
</div>
在CSS:
.container {
width: 560px;
height: 160px;
overflow: hidden;
}
一起
整体看起来像这样:
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function remainder(a, b) {
return a - Math.floor(a / b) * b;
}
function moveMap(){
const sector = {
x: Math.floor(-remainder(MAIN.position.x, tileSize)),
y: Math.floor(-remainder(MAIN.position.y, tileSize))
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
drawMap();
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
.container {
width: 560px;
height: 160px;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<canvas id=canvas></canvas>
</div>