Skip to content

Instantly share code, notes, and snippets.

@lumixraku
Created February 12, 2024 07:14
Show Gist options
  • Save lumixraku/bdcf9aafea54dc98596fd24797ab68f8 to your computer and use it in GitHub Desktop.
Save lumixraku/bdcf9aafea54dc98596fd24797ab68f8 to your computer and use it in GitHub Desktop.
Free Draw
<!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