Сводная информация
Я считаю, что каждый должен был столкнуться с такой потребностью при изучении канваса или использовании канваса в разработке проекта: реализовать виджет чертежной доски, который можно написать.
Что ж, я считаю, что это можно сделать с помощью всего нескольких десятков строк кода для детей, знакомых с холстом.Следующая демонстрация является простым примером:
<!DOCTYPE html>
<html>
<head>
<title>Sketchpad demo</title>
<style type="text/css">
canvas {
border: 1px blue solid;
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="500"></canvas>
<script type="text/javascript">
let isDown = false;
let beginPoint = null;
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
// 设置线条颜色
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
canvas.addEventListener('mousedown', down, false);
canvas.addEventListener('mousemove', move, false);
canvas.addEventListener('mouseup', up, false);
canvas.addEventListener('mouseout', up, false);
function down(evt) {
isDown = true;
beginPoint = getPos(evt);
}
function move(evt) {
if (!isDown) return;
const endPoint = getPos(evt);
drawLine(beginPoint, endPoint);
beginPoint = endPoint;
}
function up(evt) {
if (!isDown) return;
const endPoint = getPos(evt);
drawLine(beginPoint, endPoint);
beginPoint = null;
isDown = false;
}
function getPos(evt) {
return {
x: evt.clientX,
y: evt.clientY
}
}
function drawLine(beginPoint, endPoint) {
ctx.beginPath();
ctx.moveTo(beginPoint.x, beginPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
ctx.stroke();
ctx.closePath();
}
</script>
</body>
</html>
Логика его реализации также очень проста:
- Мы в основном отслеживаем три события на холсте холста:
mousedown
,mouseup
а такжеmousemove
, а также создаемisDown
Переменная; - Когда пользователь нажимает мышь (
mousedown
, т.е. начать писать) будетisDown
установлен вtrue
, при падении мыши (mouseup
), установите его наfalse
, преимущество этого заключается в том, что он может определить, находится ли пользователь в настоящее время в состоянии рисования; - пройти через
mousemove
Событие непрерывно собирает точки координат, через которые проходит мышь, тогда и только тогда, когдаisDown
дляtrue
(то есть в состоянии записи) передать текущую точку через холстlineTo
Метод соединяется и рисует с предыдущей точкой;
С помощью описанных выше шагов мы можем реализовать базовую функцию чертежной доски, но все не так просто, осторожная детская обувь может столкнуться с очень серьезной проблемой - линии, нарисованные таким образом, неровные, недостаточно гладкие, и чем быстрее вы рисуете, тем сильнее ощущение ломаных линий. Производительность показана на следующем рисунке:
Почему это так?
анализ проблемы
Основными причинами этого явления являются:
- Мы основаны на холсте
lineTo
Если метод соединяет точки, соединение между двумя соседними точками представляет собой прямую линию, а не кривую, поэтому то, что нарисовано таким образом, является ломаной линией; - Ограничено парой браузеров
mousemove
Частота сбора событий, мы все знаем, что вmousemove
, браузер собирает координаты текущей мыши через каждые короткие промежутки времени, поэтому чем быстрее движется мышь, тем дальше расстояние между двумя соседними собранными точками, поэтому «тем очевиднее смысл ломаных линий»;
Как нарисовать плавную кривую?
На самом деле есть способ нарисовать плавную кривую.lineTo
Ненадежно, мы можем использовать другой API рисования холста -quadraticCurveTo
, который используется для рисования квадратичных кривых Безье.
Квадратичная кривая Безье
quadraticCurveTo(cp1x, cp1y, x, y)
передачаquadraticCurveTo
Метод требует четыре параметра,cp1x
,cp1y
описывает контрольные точки, аx
,y
является конечной точкой кривой:
Более подробную информацию можно перенестиMDN
Поскольку мы хотим использовать кривую Безье, очевидно, что наших данных недостаточно,Чтобы полностью описать квадратичную кривую Безье, нам нужны: начальная точка, контрольная точка и конечная точка., откуда такие данные?
Есть изящный алгоритм, который помогает нам получить эту информацию.
Алгоритм получения квадратичных ключевых точек Безье
Этот алгоритм понять не сложно, тут я прямо привожу пример:
- Предположим, мы собираем в общей сложности 6 координат мыши на одном рисунке, которые
A, B, C, D, E, F
; - занять фронт
A, B, C
три балла, рассчитатьB
а такжеC
серединаB1
,кA
в качестве отправной точки,B
является контрольной точкой,B1
в качестве конечной точки используйтеquadraticCurveTo
Нарисуйте сегмент квадратичной кривой Безье; - Далее вычисляем
C
а такжеD
середина точкиC1
,кB1
в качестве отправной точки,C
для пункта управления,C1
Продолжайте рисовать кривую для конечной точки; - И так далее и так далее продолжаем рисовать, когда будет достигнута последняя точка
F
, затем сD
а такжеE
серединаD1
в качестве отправной точки сE
является контрольной точкой,F
Завершите кривую Безье для конечной точки.
Хорошо, алгоритм такой, далее мы снова обновим существующий код на основе этого алгоритма:
let isDown = false;
let points = [];
let beginPoint = null;
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
// 设置线条颜色
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
canvas.addEventListener('mousedown', down, false);
canvas.addEventListener('mousemove', move, false);
canvas.addEventListener('mouseup', up, false);
canvas.addEventListener('mouseout', up, false);
function down(evt) {
isDown = true;
const { x, y } = getPos(evt);
points.push({x, y});
beginPoint = {x, y};
}
function move(evt) {
if (!isDown) return;
const { x, y } = getPos(evt);
points.push({x, y});
if (points.length > 3) {
const lastTwoPoints = points.slice(-2);
const controlPoint = lastTwoPoints[0];
const endPoint = {
x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2,
y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2,
}
drawLine(beginPoint, controlPoint, endPoint);
beginPoint = endPoint;
}
}
function up(evt) {
if (!isDown) return;
const { x, y } = getPos(evt);
points.push({x, y});
if (points.length > 3) {
const lastTwoPoints = points.slice(-2);
const controlPoint = lastTwoPoints[0];
const endPoint = lastTwoPoints[1];
drawLine(beginPoint, controlPoint, endPoint);
}
beginPoint = null;
isDown = false;
points = [];
}
function getPos(evt) {
return {
x: evt.clientX,
y: evt.clientY
}
}
function drawLine(beginPoint, controlPoint, endPoint) {
ctx.beginPath();
ctx.moveTo(beginPoint.x, beginPoint.y);
ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
ctx.stroke();
ctx.closePath();
}
На основе оригинала мы создали переменнуюpoints
перед сохранениемmousemove
Точка, через которую проходит мышь в событии, по алгоритму видно, что для построения квадратичной кривой Безье необходимо как минимум 3 точки, поэтому нам осталось толькоpoints
Розыгрыш начинается, когда количество очков больше 3. Дальнейшая обработка аналогична алгоритму и здесь повторяться не будет.
После обновления кода наша кривая также стала намного более гладкой, как показано на следующем рисунке:
Эта статья закончилась, надеюсь, вы хорошо проведете время, «рисуя» на доске для рисования на холсте ~ увидимся в следующий раз :)
Заинтересованная детская обувь можеткликните сюдаСледите за моим блогом, любые свежие и интересные посты в блоге будут публиковаться здесь как можно скорее~