Created
February 12, 2024 07:14
-
-
Save lumixraku/bdcf9aafea54dc98596fd24797ab68f8 to your computer and use it in GitHub Desktop.
Free Draw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>上下分层提高 Canvas 书写性能</title> | |
<style> | |
body, | |
html { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
} | |
#app-container { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
} | |
#draw { | |
border: 1px solid black; | |
position: absolute; | |
z-index: 9999; | |
} | |
#draw-content { | |
border: 1px solid black; | |
position: absolute; | |
z-index: 9998; | |
pointer-events: none; | |
} | |
</style> | |
<!-- <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> | |
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> --> | |
</head> | |
<body> | |
<!-- 动态层 Canvas --> | |
<canvas id="draw"></canvas> | |
<!-- 静态层 Canvas --> | |
<canvas id="draw-content"></canvas> | |
<script> | |
let start = false; // 是否开始绘制 | |
let points = []; // 记录鼠标移动的点 | |
let history = []; // 记录之前绘制的内容 | |
const dpr = window.devicePixelRatio || 1; | |
const canvas = document.getElementById('draw'); | |
const canvasContent = document.getElementById('draw-content'); | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
// 上层 canvas 用来动态绘制绘制鼠标移动的轨迹 | |
canvas.width = Math.round(width * dpr); | |
canvas.height = Math.round(height * dpr); | |
canvas.style.width = width + "px"; | |
canvas.style.height = height + "px"; | |
const ctx = canvas.getContext('2d'); | |
ctx.scale(dpr, dpr); | |
// 上层 canvas end | |
// 下层 canvas 用来保存绘制的内容 | |
canvasContent.width = Math.round(width * dpr); | |
canvasContent.height = Math.round(height * dpr); | |
canvasContent.style.width = width + "px"; | |
canvasContent.style.height = height + "px"; | |
const ctxContent = canvasContent.getContext('2d'); | |
ctxContent.scale(dpr, dpr); | |
// 下层 canvas end | |
/** | |
* 自由画笔的实现思路 | |
* 1 监听鼠标事件 | |
* 2 将鼠标移动的轨迹记录下来 | |
* 3 然后将这些点连接成线 | |
*/ | |
canvas.addEventListener('pointerdown', (e) => { | |
start = true; // 通过监听鼠标按下事件,来判断是否开始绘制 | |
addPoint((e)); // 将鼠标按下的点添加到points数组中 | |
}); | |
canvas.addEventListener('pointermove', (e) => { | |
if (!start) return; // 如果没有按下,则不绘制 | |
addPoint((e)); // 将鼠标移动的点添加到points数组中 | |
renderUpperCanvas(ctx); // 绘制上层 | |
}); | |
canvas.addEventListener('pointerup', (e) => { | |
start = false; | |
// 将上层 canvas 绘制的内容保存到下层 canvas 中 | |
history.push(points); | |
points = []; // 绘制完毕后,清空points数组 | |
renderLowerCanvas(ctx, ctxContent); | |
}); | |
/* | |
* 将鼠标事件的点转化为相对于canvas的坐标上的点 | |
*/ | |
function addPoint(e) { | |
points.push({ | |
x: e.clientX, | |
y: e.clientY | |
}); | |
} | |
/** | |
* 绘制函数 | |
* @param {*} ctx - canvas 尺寸 | |
* @param {*} points - 鼠标移动的点集 | |
*/ | |
function render(ctx, points) { | |
ctx.strokeStyle = 'red'; // 设置线条颜色 | |
ctx.lineWidth = 6; // 设置线条宽度 | |
ctx.lineJoin = 'round'; // 设置线条连接处的样式 | |
ctx.lineCap = 'round'; // 设置线条末端的样式 | |
/* | |
beginPath() 是 Canvas 2D API 中的一个方法,用于开始一个新的路径。当你想创建一个新的路径时,你需要调用这个方法。 | |
例如,你可能会这样使用它: | |
context.beginPath(); | |
context.moveTo(50, 50); | |
context.lineTo(200, 50); | |
context.stroke(); | |
在这个例子中,beginPath() 开始一个新的路径,moveTo(50, 50) 将路径的起点移动到 (50, 50),lineTo(200, 50) 添加一条从当前位置到 (200, 50) 的线, | |
最后 stroke() 方法绘制出路径。 | |
其中 context 是你的 canvas 上下文。 | |
*/ | |
ctx.beginPath(); // 开始绘制 | |
ctx.moveTo(points[0].x, points[0].y); // 将画笔移动到起始点 | |
for (let i = 1; i < points.length; i++) { | |
// 取终点,将上一个点作为控制点,平滑过渡 | |
const cx = (points[i].x + points[i - 1].x) / 2; | |
const cy = (points[i].y + points[i - 1].y) / 2; | |
ctx.quadraticCurveTo(points[i - 1].x, points[i - 1].y, cx, cy); | |
} | |
ctx.stroke(); // 绘制路径 | |
} | |
function renderUpperCanvas(ctx) { | |
if (!points.length) return; | |
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); // 清空画布 | |
render(ctx, points); | |
} | |
function renderLowerCanvas(ctx, ctxContent) { | |
if (!history.length) return; | |
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); // 清空画布 | |
ctxContent.clearRect(0, 0, window.innerWidth, window.innerHeight); // 清空画布 | |
history.forEach(points => { | |
console.log(`points--->`, points); | |
render(ctxContent, points); | |
}); | |
} | |
function throttle(fn, delay = 16 / 1000) { | |
let timer = null; | |
return function () { | |
if (timer) return; | |
timer = setTimeout(() => { | |
fn.apply(this, arguments); | |
timer = null; | |
}, delay); | |
} | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment