绘制网格一般可以用 3 种方式绘制, css、svg、canvas
1. 通过 css 利用linear-grident 绘制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
.grid {
margin: 30px auto;
width: 500px;
height: 500px; /* 垂直线 */ /* 水平线 */
background-image: linear-gradient(to right, transparent 19px, #ccc 100%), linear-gradient(to bottom, transparent 19px, #ccc 100%);
background-size: 20px 20px;
background-repeat: repeat;
}
</style>
</head>
<body>
<div class="grid"></div>
</body>
</html>
原理:
- linear-gradient(to right, transparent 19px, #ccc 100%) 绘制垂直线
- linear-gradient(to bottom, transparent 19px, #ccc 100%) 绘制水平线
如果要绘制大于1px的网格线可以这样写css
.grid-line {
background-image: linear-gradient(to right, transparent 18px, #ccc 18px, #ccc 100%), linear-gradient(to bottom, transparent 18px, #ccc 18px, #ccc 100%);
background-size: 20px 20px;
background-repeat: repeat;
}
通过 svg 利用 pattern 填充
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<svg width="500" height="500">
<defs>
<pattern id="Pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<!-- 垂直线 -->
<line stroke="#ccc" fill="transparent" x1="19" y1="0" x2="19" y2="20"></line>
<!-- 水平线 -->
<line stroke="#ccc" fill="transparent" x1="0" y1="19" x2="20" y2="19"></line>
</pattern>
</defs>
<rect fill="url(#Pattern)" x="0" y="0" width="500" height="500"></rect>
</svg>
</body>
</html>
效果如下同 css grid
3. 通过 canvas 绘制 createPattern 绘制
用 canvas 绘制网格相对复杂,createPattern 参数需要传入一个 Image 对象或者另外一个canvas 元素,一般来说绘制网格直接使用css,或者svg即可 这里直接给出代码,感兴趣的可以看看
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" height="100%">
<defs>
<pattern id="Pattern" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
<line stroke="#ccc" fill="transparent" x1="39" y1="0" x2="39" y2="40"></line>
<line stroke="#ccc" fill="transparent" x1="0" y1="39" x2="40" y2="39"></line>
</pattern>
</defs>
<rect fill="url(#Pattern)" x="0" y="0" width="100%" height="100%"></rect>
</svg>`
const base64 = window.btoa(svg);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const WIDTH = 500;
const HEIGHT = 500;
canvas.width = WIDTH * 2;
canvas.height = HEIGHT * 2;
canvas.style.width = WIDTH + 'px';
canvas.style.height = HEIGHT + 'px';
// ctx.scale(devicePixelRatio, devicePixelRatio);
document.body.appendChild(canvas);
const image = new Image();
image.src = `data:image/svg+xml;base64,${base64}`;
image.onload = function () {
const pattern = ctx.createPattern(image,'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
</script>
</body>
</html>
绘制无限网格
无限网格是指,经过拖拽,缩放后,网格格子始终铺满整个渲染区域, 下面 以 html + css + js 的方式为例绘制无限网格。
- 让网格动起来,实现拖拽 通过改变 background-position 的方式让网格动起来
function listen(el, type, fn) {
el.addEventListener(type, fn);
return () => {
el.removeEventListener(type, fn);
}
}
const gridEl = document.getElementById('grid');
// 监听 mousedown, mousemove, mouseup 实现拖拽
// 记录鼠标按下的位置
let mousedownInfo
// background-position 初始偏移 offsetX, offsetY
const grid = {
offsetX: 0,
offsetY: 0,
scale: 1,
size: 20,
baseSize: 20,
setGridOffset(offsetX, offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
gridEl.style.backgroundPosition = `${offsetX}px ${offsetY}px`;
},
adjustOffset() {
// 调整背景的位置,使其数值不会过大, 背景位置 x 和 背景位置 x % grid.size 的效果一样
grid.setGridOffset(grid.offsetX % grid.size, grid.offsetY % grid.size);
}
},
// 初始化网格,让网格居中
initGrid();
function initGrid() {
const rect = gridEl.getBoundingClientRect();
const gridSize = grid.size;
const rowCount = Math.floor(rect.height / gridSize);
const colCount = Math.floor(rect.width / gridSize);
const startY = -((rowCount + 1) * gridSize - rect.height) / 2;
const startX = -((colCount + 1) * gridSize - rect.width) / 2;
// 设置网格线
gridEl.style.backgroundImage = `linear-gradient(to right, transparent ${size - 1}px, #ccc 100%), linear-gradient(to bottom, transparent ${size - 1}px, #ccc 100%);`
// 让网格居中显示
grid.setGridOffset(startX, startY);
}
// 实现拖拽
listen(gridEl, 'mousedown', (e) => {
mousedownInfo = {
clientX: e.clientX,
clientY: e.clientY,
}
const rect = gridEl.getBoundingClientRect();
const startX = grid.offsetX;
const startY = grid.offsetY;
const offMove = listen(document, 'mousemove', (e) => {
const moveX = e.clientX - mousedownInfo.clientX;
const moveY = e.clientY - mousedownInfo.clientY;
// 更新background-position 让网格动起来
grid.setGridOffset(moveX + startX, moveY + startY);
});
const offUp = listen(document, 'mouseup', (e) => {
grid.adjustOffset();
offMove();
offUp();
})
});
- 实现缩放,监听鼠标滚轮
为grid对象增加缩放函数
const grid = {
// 指定缩放中心
setGridScale(scale, origin) {
const factor = scale / this.scale;
const size = grid.baseSize * scale;
const h = origin.x - grid.offsetX;
const v = origin.y - grid.offsetY;
const offsetX = origin.x - h * factor;
const offsetY = origin.y - v * factor;
gridEl.style.cssText += `
background-image: linear-gradient(to right, transparent ${size - 1}px, #ccc 100%), linear-gradient(to bottom, transparent ${size - 1}px, #ccc 100%);
background-size: ${size}px ${size}px;
background-position: ${offsetX}px ${offsetY}px;
`;
grid.size = size;
this.scale = scale;
this.offsetX = offsetX;
this.offsetY = offsetY;
},
}
let timer;
document.addEventListener('mousewheel', (e) => {
e.preventDefault();
const delta = e.wheelDelta / 120;
const rect = gridEl.getBoundingClientRect();
// 以鼠标位置为缩放中心缩放
grid.setGridScale(grid.scale + delta * 0.1, {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
clearTimeout(timer);
timer = setTimeout(() => {
grid.adjustOffset();
}, 500)
}, {
passive: false,
})
指定缩放中心进行缩放的计算的图示 参考: www.cnblogs.com/3body/p/943…
全部代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
width: 100%;
height: 100%;
}
body {
margin: 0;
}
.grid {
margin: 30px auto;
width: 500px;
height: 500px;
background-repeat: repeat;
}
</style>
</head>
<body>
<div id="grid" class="grid"></div>
<script>
const gridEl = document.getElementById('grid');
let mousedownInfo
const grid = {
offsetX: 0,
offsetY: 0,
scale: 1,
size: 20,
baseSize: 20,
setGridOffset(offsetX, offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
gridEl.style.backgroundPosition = `${offsetX}px ${offsetY}px`;
},
setGridScale(scale, origin) {
const factor = scale / this.scale;
const size = grid.baseSize * scale;
const h = origin.x - grid.offsetX;
const v = origin.y - grid.offsetY;
const offsetX = origin.x - h * factor;
const offsetY = origin.y - v * factor;
gridEl.style.cssText += `
background-image: linear-gradient(to right, transparent ${size - 1}px, #ccc 100%), linear-gradient(to bottom, transparent ${size - 1}px, #ccc 100%);
background-size: ${size}px ${size}px;
background-position: ${offsetX}px ${offsetY}px;
`;
grid.size = size;
this.scale = scale;
this.offsetX = offsetX;
this.offsetY = offsetY;
},
getGridOffset() {
return this;
},
adjustOffset() {
// 调整背景的位置,使其数值不会过大, 背景位置 x 和 背景位置 x % grid.size 的效果一样
grid.setGridOffset(grid.offsetX % grid.size, grid.offsetY % grid.size);
}
}
initGrid();
listen(gridEl, 'mousedown', (e) => {
mousedownInfo = {
clientX: e.clientX,
clientY: e.clientY,
}
const rect = gridEl.getBoundingClientRect();
const startX = grid.offsetX;
const startY = grid.offsetY;
const offMove = listen(document, 'mousemove', (e) => {
const moveX = e.clientX - mousedownInfo.clientX;
const moveY = e.clientY - mousedownInfo.clientY;
grid.setGridOffset(moveX + startX, moveY + startY);
});
const offUp = listen(document, 'mouseup', (e) => {
grid.adjustOffset();
offMove();
offUp();
})
})
let timer;
document.addEventListener('mousewheel', (e) => {
e.preventDefault();
const delta = e.wheelDelta / 120;
const rect = gridEl.getBoundingClientRect();
// 以鼠标位置为缩放中心缩放
grid.setGridScale(grid.scale + delta * 0.1, {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
clearTimeout(timer);
timer = setTimeout(() => {
grid.adjustOffset();
}, 500)
}, {
passive: false,
})
function initGrid() {
const rect = gridEl.getBoundingClientRect();
const gridSize = 20;
const rowCount = Math.floor(rect.height / gridSize);
const colCount = Math.floor(rect.width / gridSize);
const startY = -((rowCount + 1) * gridSize - rect.height) / 2;
const startX = -((colCount + 1) * gridSize - rect.width) / 2;
grid.setGridScale(1, {
x: 0,
y: 0,
})
grid.setGridOffset(startX, startY);
}
function listen(el, type, fn) {
el.addEventListener(type, fn);
return () => {
el.removeEventListener(type, fn);
}
}
</script>
</body>
</html>