Canvas 网格中的位置不准确
Canvas position in grid inaccurate
canvas 上网格的 x 和 y 位置在某些单元格上被置于左侧 1 行和上方 1 列。我不知道是什么原因造成的,但它主要发生在网格的中间到接近末端的地方。
'use strict';
// the canvas
const canvas = document.querySelector('.canvas');
const context = canvas.getContext('2d');
class Node {
constructor(row, col) {
// save current index (row, col) to know where in the canvas it is
this.row = row;
this.col = col;
this.show = color => {
context.beginPath();
context.rect(this.row * cellDimension, this.col * cellDimension, cellDimension - 1, cellDimension - 1);
context.fillStyle = color;
context.fill();
}
}
}
// the counter from input field
const colRowCount = 60;
// will be determined from setup
let cellDimension;
let gridArray = new Array(colRowCount);
//start and finish
let startNode = new Node();
let endNode = new Node();
// array of the path nodes
let totalPath = new Array();
// setup all the neccessities
const setup = () => {
// subtract the height of menu
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
cellDimension = canvas.width / colRowCount;
for (let i = 0; i < colRowCount; i++) {
gridArray[i] = new Array(colRowCount);
for (let j = 0; j < colRowCount; j++) {
gridArray[i][j] = new Node(i, j);
}
}
context.beginPath();
for (let i = 0; i < canvas.width; i += cellDimension) {
// horizontal divider
context.moveTo(0, i);
context.lineTo(canvas.width, i);
// vertical divider
context.moveTo(i, 0);
context.lineTo(i, canvas.width);
context.strokeStyle = '#ddd';
context.stroke();
}
}
const getMousePosition = event => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// top left
let cellX = 0;
let cellY = 0;
// bounds of the square unit
const bounds = Math.floor(Math.floor(canvas.width) / colRowCount);
for (let i = 1; i < colRowCount; i++) {
if (x > (bounds * i)) {
cellX = i;
}
if (y > (bounds * i)) {
cellY = i;
}
}
return {
x: cellX,
y: cellY
};
}
const placeStart = position => {
startNode = gridArray[position.x][position.y];
startNode.show('green');
}
window.addEventListener('load', () => {
// setup the canvas
setup();
// event listener for canvas
canvas.addEventListener('mousedown', event => {
placeStart(getMousePosition(event));
});
});
.container {
height: calc(100% - 50px);
width: 100%;
}
/*for all the grids with canvas*/
.canvas {
height: 100%;
width: 100%;
border: 1px solid green;
}
/*for all the grids with canvas*/
<div class='container'>
<canvas class='canvas'>
</canvas>
</div>
jsfiddle 中的那个表现得更出乎意料。
这里有多个问题...
首先,canvas 有两种大小,一种是它的缓冲区,由它的 width
和 height
属性设置,另一种是它的渲染大小(由 CSS).
这里两个尺寸不同,所以实际的 canvas 图像被 CSS 渲染器拉伸,导致抗锯齿和舍入不精确。 See more here.
然后,stroke()
确实在提供的坐标的两侧重叠,因此对于 1
的 lineWidth
,您需要添加(或删除)0.5px 的偏移量以便确保垂直线或水平线正确适合单个像素的边界,否则您将再次启动抗锯齿。
最后,你的坐标甚至没有四舍五入,所以你会落在两个像素坐标之间,再次添加抗锯齿...
这是解决所有问题的尝试:
'use strict';
// the canvas
const canvas = document.querySelector('.canvas');
const context = canvas.getContext('2d');
class Node {
constructor(row, col) {
// save current index (row, col) to know where in the canvas it is
this.row = row;
this.col = col;
this.show = color => {
const border_offset = context.lineWidth;
context.beginPath();
context.rect(this.row * cellDimension + border_offset, this.col * cellDimension + border_offset, cellDimension - border_offset, cellDimension - border_offset);
context.fillStyle = color;
context.fill();
// remember we're on, so we can be redrawn on resize
this.color = color;
}
}
}
// The counter from input field
const colRowCount = 60;
// Will be determined from setup
let cellDimension;
let gridArray = new Array(colRowCount);
//start and finish
let startNode = new Node();
let endNode = new Node();
// array of the path nodes
let totalPath = new Array();
// Initialize all the nodes directly,
// we don't need to know the size of the cells
// or of the canvas to do so.
for (let i = 0; i < colRowCount; i++) {
gridArray[i] = new Array(colRowCount);
for (let j = 0; j < colRowCount; j++) {
gridArray[i][j] = new Node(i, j);
}
}
// called at init, and at every page resize
const redraw = () => {
// get the size of the container measured by CSS
const parent = canvas.parentNode;
const parent_width = parent.offsetWidth;
// remove 1 lineWidth for outer strokes
const max_grid_size = parent_width - context.lineWidth;
// cellDimension must be an integer
cellDimension = Math.floor( max_grid_size / colRowCount );
const grid_size = cellDimension * colRowCount;
// now we know the size of our grid,
// our canvas will be one lineWidth bigger to show the outer strokes
canvas.width = canvas.height = grid_size + context.lineWidth;
context.beginPath();
const stroke_offset = context.lineWidth / 2;
for (let i = 0; i <= grid_size; i += cellDimension) {
// horizontal divider
context.moveTo(stroke_offset, i + stroke_offset);
context.lineTo(grid_size + stroke_offset, i + stroke_offset);
// vertical divider
context.moveTo(i + stroke_offset, stroke_offset);
context.lineTo(i + stroke_offset, grid_size + stroke_offset );
}
// stroke only once
context.strokeStyle = '#ddd';
context.stroke();
// redraw all the nodes that were already active
gridArray.flat().filter( (node) => node.color )
.forEach( (node) => node.show( node.color ) );
}
const getMousePosition = event => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// top left
let cellX = 0;
let cellY = 0;
// bounds of the square unit
const bounds = Math.floor(Math.floor(canvas.width) / colRowCount);
for (let i = 1; i < colRowCount; i++) {
if (x > (bounds * i)) {
cellX = i;
}
if (y > (bounds * i)) {
cellY = i;
}
}
return {
x: cellX,
y: cellY
};
}
const placeStart = position => {
startNode = gridArray[position.x][position.y];
startNode.show('green');
}
window.addEventListener('load', () => {
redraw();
// event listener for canvas
canvas.addEventListener('mousedown', event => {
placeStart(getMousePosition(event));
});
window.onresize = redraw;
});
.container {
height: calc(100% - 50px);
width: 100%;
}
.canvas {
border: 1px solid green;
/* we let the canvas buffer's size rule its presentation dimensions */
}
<div class='container'>
<canvas class='canvas'></canvas>
</div>
canvas 上网格的 x 和 y 位置在某些单元格上被置于左侧 1 行和上方 1 列。我不知道是什么原因造成的,但它主要发生在网格的中间到接近末端的地方。
'use strict';
// the canvas
const canvas = document.querySelector('.canvas');
const context = canvas.getContext('2d');
class Node {
constructor(row, col) {
// save current index (row, col) to know where in the canvas it is
this.row = row;
this.col = col;
this.show = color => {
context.beginPath();
context.rect(this.row * cellDimension, this.col * cellDimension, cellDimension - 1, cellDimension - 1);
context.fillStyle = color;
context.fill();
}
}
}
// the counter from input field
const colRowCount = 60;
// will be determined from setup
let cellDimension;
let gridArray = new Array(colRowCount);
//start and finish
let startNode = new Node();
let endNode = new Node();
// array of the path nodes
let totalPath = new Array();
// setup all the neccessities
const setup = () => {
// subtract the height of menu
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
cellDimension = canvas.width / colRowCount;
for (let i = 0; i < colRowCount; i++) {
gridArray[i] = new Array(colRowCount);
for (let j = 0; j < colRowCount; j++) {
gridArray[i][j] = new Node(i, j);
}
}
context.beginPath();
for (let i = 0; i < canvas.width; i += cellDimension) {
// horizontal divider
context.moveTo(0, i);
context.lineTo(canvas.width, i);
// vertical divider
context.moveTo(i, 0);
context.lineTo(i, canvas.width);
context.strokeStyle = '#ddd';
context.stroke();
}
}
const getMousePosition = event => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// top left
let cellX = 0;
let cellY = 0;
// bounds of the square unit
const bounds = Math.floor(Math.floor(canvas.width) / colRowCount);
for (let i = 1; i < colRowCount; i++) {
if (x > (bounds * i)) {
cellX = i;
}
if (y > (bounds * i)) {
cellY = i;
}
}
return {
x: cellX,
y: cellY
};
}
const placeStart = position => {
startNode = gridArray[position.x][position.y];
startNode.show('green');
}
window.addEventListener('load', () => {
// setup the canvas
setup();
// event listener for canvas
canvas.addEventListener('mousedown', event => {
placeStart(getMousePosition(event));
});
});
.container {
height: calc(100% - 50px);
width: 100%;
}
/*for all the grids with canvas*/
.canvas {
height: 100%;
width: 100%;
border: 1px solid green;
}
/*for all the grids with canvas*/
<div class='container'>
<canvas class='canvas'>
</canvas>
</div>
jsfiddle 中的那个表现得更出乎意料。
这里有多个问题...
首先,canvas 有两种大小,一种是它的缓冲区,由它的 width
和 height
属性设置,另一种是它的渲染大小(由 CSS).
这里两个尺寸不同,所以实际的 canvas 图像被 CSS 渲染器拉伸,导致抗锯齿和舍入不精确。 See more here.
然后,stroke()
确实在提供的坐标的两侧重叠,因此对于 1
的 lineWidth
,您需要添加(或删除)0.5px 的偏移量以便确保垂直线或水平线正确适合单个像素的边界,否则您将再次启动抗锯齿。
最后,你的坐标甚至没有四舍五入,所以你会落在两个像素坐标之间,再次添加抗锯齿...
这是解决所有问题的尝试:
'use strict';
// the canvas
const canvas = document.querySelector('.canvas');
const context = canvas.getContext('2d');
class Node {
constructor(row, col) {
// save current index (row, col) to know where in the canvas it is
this.row = row;
this.col = col;
this.show = color => {
const border_offset = context.lineWidth;
context.beginPath();
context.rect(this.row * cellDimension + border_offset, this.col * cellDimension + border_offset, cellDimension - border_offset, cellDimension - border_offset);
context.fillStyle = color;
context.fill();
// remember we're on, so we can be redrawn on resize
this.color = color;
}
}
}
// The counter from input field
const colRowCount = 60;
// Will be determined from setup
let cellDimension;
let gridArray = new Array(colRowCount);
//start and finish
let startNode = new Node();
let endNode = new Node();
// array of the path nodes
let totalPath = new Array();
// Initialize all the nodes directly,
// we don't need to know the size of the cells
// or of the canvas to do so.
for (let i = 0; i < colRowCount; i++) {
gridArray[i] = new Array(colRowCount);
for (let j = 0; j < colRowCount; j++) {
gridArray[i][j] = new Node(i, j);
}
}
// called at init, and at every page resize
const redraw = () => {
// get the size of the container measured by CSS
const parent = canvas.parentNode;
const parent_width = parent.offsetWidth;
// remove 1 lineWidth for outer strokes
const max_grid_size = parent_width - context.lineWidth;
// cellDimension must be an integer
cellDimension = Math.floor( max_grid_size / colRowCount );
const grid_size = cellDimension * colRowCount;
// now we know the size of our grid,
// our canvas will be one lineWidth bigger to show the outer strokes
canvas.width = canvas.height = grid_size + context.lineWidth;
context.beginPath();
const stroke_offset = context.lineWidth / 2;
for (let i = 0; i <= grid_size; i += cellDimension) {
// horizontal divider
context.moveTo(stroke_offset, i + stroke_offset);
context.lineTo(grid_size + stroke_offset, i + stroke_offset);
// vertical divider
context.moveTo(i + stroke_offset, stroke_offset);
context.lineTo(i + stroke_offset, grid_size + stroke_offset );
}
// stroke only once
context.strokeStyle = '#ddd';
context.stroke();
// redraw all the nodes that were already active
gridArray.flat().filter( (node) => node.color )
.forEach( (node) => node.show( node.color ) );
}
const getMousePosition = event => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// top left
let cellX = 0;
let cellY = 0;
// bounds of the square unit
const bounds = Math.floor(Math.floor(canvas.width) / colRowCount);
for (let i = 1; i < colRowCount; i++) {
if (x > (bounds * i)) {
cellX = i;
}
if (y > (bounds * i)) {
cellY = i;
}
}
return {
x: cellX,
y: cellY
};
}
const placeStart = position => {
startNode = gridArray[position.x][position.y];
startNode.show('green');
}
window.addEventListener('load', () => {
redraw();
// event listener for canvas
canvas.addEventListener('mousedown', event => {
placeStart(getMousePosition(event));
});
window.onresize = redraw;
});
.container {
height: calc(100% - 50px);
width: 100%;
}
.canvas {
border: 1px solid green;
/* we let the canvas buffer's size rule its presentation dimensions */
}
<div class='container'>
<canvas class='canvas'></canvas>
</div>