小怪兽爱吃肉
画布填充(CanvasPatterns 和 CanvasGradients)始终相对于上下文的变换矩阵,因此它们确实默认位于画布的左上角,并且并不真正关心使用它们的路径在哪里。const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');const img = new Image();img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!img.crossOrigin = "anonymous";img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";function begin() { const rect_size = 20; ctx.fillStyle = ctx.createPattern( img, 'no-repeat' ); // drawing a checkerboard of several rects shows that the pattern doesn't move for ( let y = 0; y < canvas.height; y += rect_size ) { for ( let x = (y / rect_size % 2) ? rect_size : 0 ; x < canvas.width; x += rect_size * 2 ) { ctx.fillRect( x, y, rect_size, rect_size ); } }}<canvas id="canvas" width="500" height="500"></canvas>现在,因为它们与上下文变换矩阵相关,这意味着我们还可以通过更改该变换矩阵来移动它们:const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');const img = new Image();img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!img.crossOrigin = "anonymous";img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Cool_bunny_sprite.png";function begin() { const rect_size = 244; const max_speed = 5; let x_speed = Math.random() * max_speed; let y_speed = Math.random() * max_speed; ctx.fillStyle = ctx.createPattern( img, 'repeat' ); let x = 0; let y = 0; requestAnimationFrame( anim ); function anim( now ) { clear(); x = (x + x_speed); y = (y + y_speed); if( x > canvas.width || x < -rect_size ) { x_speed = Math.random() * max_speed * -Math.sign( x_speed ); x = Math.min( Math.max( x, -rect_size ), canvas.width ); } if( y > canvas.height || y < -rect_size ) { y_speed = Math.random() * max_speed * -Math.sign( y_speed ) y = Math.min( Math.max( y, -rect_size ), canvas.height ); } // we change the transformation matrix of our context ctx.setTransform( 1, 0, 0, 1, x, y ); // we thus always draw at coords 0,0 ctx.fillRect( 0, 0, rect_size, rect_size ); ctx.strokeRect( 0, 0, rect_size, rect_size ); requestAnimationFrame( anim ); } function clear() { // since we changed the tranform matrix we need to reset it to identity ctx.setTransform( 1, 0, 0, 1, 0, 0 ); ctx.clearRect( 0, 0, canvas.width, canvas.height ); }}<canvas id="canvas" width="300" height="300"></canvas>我们甚至可以通过在声明子路径后更改变换矩阵来将路径声明与填充分离,当然也可以将简写替换 fillRect()为beginPath(); rect(); fill()const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');const img = new Image();img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!img.crossOrigin = "anonymous";img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Cool_bunny_sprite.png";function begin() { const rect_size = 244; const max_speed = 5; let x_speed = Math.random() * max_speed; let y_speed = Math.random() * max_speed; ctx.fillStyle = ctx.createPattern( img, 'repeat' ); let x = 0; let y = 0; requestAnimationFrame( anim ); function anim( now ) { clear(); x = (x + x_speed); y = (y + y_speed); if( x > canvas.width || x < -rect_size ) { x_speed = Math.random() * max_speed * -Math.sign( x_speed ); x = Math.min( Math.max( x, -rect_size ), canvas.width ); } if( y > canvas.height || y < -rect_size ) { y_speed = Math.random() * max_speed * -Math.sign( y_speed ) y = Math.min( Math.max( y, -rect_size ), canvas.height ); } // we declare the sub-path first, with identity matrix applied ctx.beginPath(); ctx.rect( 50, 50, rect_size, rect_size ); // we change the transformation matrix of our context ctx.setTransform( 1, 0, 0, 1, x, y ); // and now we fill ctx.fill(); ctx.stroke(); requestAnimationFrame( anim ); } function clear() { // since we changed the tranform matrix we need to reset it to identity ctx.setTransform( 1, 0, 0, 1, 0, 0 ); ctx.clearRect( 0, 0, canvas.width, canvas.height ); }}<canvas id="canvas" width="300" height="300"></canvas>但就你而言,听起来改变整个绘图是最简单、最惯用的方法。不太确定为什么要修改相对于相机的 x 和 y 坐标。一般来说,如果我们使用相机对象,那么场景中的对象不必关心它,并且与世界保持相对关系。