Подумайте о местном происхождении
Когда вы получаете план здания, вы не получаете гигантский лист бумаги с планом в углу, потому что вы строите в клочьях, вы хотите сдвинуть некоторые окна.летнего солнца, вы не перерисовываете план с новыми координатами для каждой стены.
Нет, вы получаете план, который умещается на небольшом листе, на плане есть местоположение и ориентация.Положение стен привязано к местным координатам плана.
То же самое относится и к рисованию в 2D.Вы можете определить прямоугольник как 4 точки вокруг начала координат.[[-10,-10],[10,-10],[10,10],[-10,10]]
и когда вы рисуете его, вы устанавливаете его местоположение и ориентацию, вы не меняете положение каждой точки на новое местоположение.
Рисуете локальную координату в мире с помощью setTransform
В2D API положение и ориентация задаются с помощью преобразования.
function drawPath(x,y, points) { // only position changes
ctx.setTransform(1,0,0,1,x,y); // set the location
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x,y);
}
ctx.stroke();
}
const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
drawPath(100, 100, box);
И с масштабированием и поворотом
function drawPath(x,y,scale, rotate, points) {
const xdx = Math.cos(rotate) * scale;
const xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x,y);
}
ctx.stroke();
}
drawPath(100, 100, 2, 0.5, box);
const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
ctx.font = "2opx arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "red";
const rand = v => Math.random() * v;
drawRandom();
function drawPath(x, y,scale, rotate, points) {
const xdx = Math.cos(rotate) * scale;
const xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
ctx.fillText("Hi",0,0);
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.setTransform(1, 0, 0, 1, 0, 0); // Resets so line width remains 1 px
ctx.stroke();
}
function drawRandom() {
drawPath(rand(W), rand(H), rand(2) + 0.5, rand(Math.PI * 2), box);
setTimeout(drawRandom, 500);
}
canvas {
border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>
Все, что вам нужно, это ctx.setTransform
и, возможно, ctx.transform
, если вы выполняете фальсификацию анимации.Я никогда не использую ctx.translate
, ctx.scale
, ctx.rotate
, потому что они медленные, и трудно представить, где вы находитесь, о, и я сказал, что они МЕДЛЕННЫЕ !!!!
Для сбросапреобразуйте (уберите масштаб, поверните и вернитесь к 0,0) вызовите ctx.resetTransform()
или ctx.setTransform(1,0,0,1,0,0)
И еще кое-что относительно вашего подхода к коду.
Детальное кодирование
Похоже, вы хотите нарисовать график.
Ручная отрисовка каждого тика, установка стилей и десятков магических чисел и значений не доставит вам большого удовольствия.Хуже всего то, что когда приходит время вносить изменения, это будет длиться вечно.
Не повторяйте
Вам нужно думать как ленивый программист.Создавайте функции, чтобы вам не приходилось делать одно и то же снова и снова.
Определите стили один раз и назовите их
Например, установка стиля 2D-контекста - это боль.Рисунок обычно имеет только несколько различных стилей, поэтому создайте объект с именованными стилями
const styles = {
textHang: {
textAlign : "center",
textBaseline : "top",
fillStyle: "blue",
font: "16px Arial",
},
};
И функцию, которая будет устанавливать стиль
const setStyle = (style, c = ctx) => Object.assign(c, style);
Теперь вы можете установить стиль
const ctx = myCanvas.getContext("2d");
setStyle(styles, styles.textHang);
ctx.fillText("The text", 100, 100);
Базовый 2D точечный помощник
Вы работаете в 2D, и 2D использует много точек.Вы будете добавлять умножение, копирование ... 2D точек снова и снова и снова.
Сократите набор текста и охватите самые базовые 2D-задачи с помощью всего 7 функций
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
Нет строки?2D API
2D API великолепен, но отсутствует.Просто нарисовать линию безумно долго, foo bar ....
ctx.linecap = 'round';
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF9900';
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(410, 410);
ctx.stroke();
Ни в коем случае нельзя создавать функции, использовать именованные стили, не вводить координаты, использовать точки.
Некоторые распространенные 2DЗадачи как функции
const clear = (c = ctx) => (setPos(), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
c.setTransform(1,0,0,1,p.x,p.y);
for(const seg of path) { // each segment
let first = true;
for(const p of seg) { // each point
first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
}
}
}
Пример
Следующее берет все вышеперечисленное и создает 2 оси.Это может показаться слишком сложным, но по мере того, как вы добавляете сложность к своему рисунку, вы быстро обнаруживаете, что вам нужно все меньше и меньше кода.
/* Set up the context get common values eg W,H for width and height */
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
// Helper functions will use a global ctx, or pass a 2d context as last argument
// P2 is a point. I use p to mean a point
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
const setStyle = (style, c = ctx) => Object.assign(c, style);
const clear = (c = ctx) => (setPos(0, c), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
setPos(p,c);
for(const seg of path) { // each segment
let first = true;
for(const p of seg) { // each point
first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
}
}
}
const styles = { // define any of the 2D context properties you wish to set
textHang: {textAlign : "center", textBaseline : "top"},
textLeft: {textAlign : "left", textBaseline : "middle"},
markTextStyle: {fillStyle: "blue", font: "16px Arial"},
markStyle: {
strokeStyle: "black",
lineCap: "round",
lineWidth: 2,
},
};
const paths = { // Array of arrays of points. each sub array is a line segment
markLeft: [[P2(-2, 0), P2(5, 0)]],
markUp: [[P2(0, 2), P2(0, -5)]],
}
// Draw an axis from point to point, using mark to mark, lineStyle for the line
// marks is an array of names for each mark, markStyle is the style for the text marks
// markDist is the distance out (90 to the right) to put the text marks
function drawAxis(fromP, toP, mark, lineStyle, marks, markStyle, markDist) {
const norm = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / P2Dist(fromP, toP));
const step = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / (marks.length-1));
const pos = P2Copy(fromP);
setStyle(lineStyle);
ctx.beginPath();
setPos(); // without argument pos is 0,0
line(fromP, toP);
for(const m of marks) {
path(pos, mark);
P2Add(pos, step);
}
ctx.stroke();
P2Set(pos, fromP);
setStyle(markStyle);
for(const m of marks) {
setPos(pos);
ctx.fillText(m,-norm.y * markDist, norm.x * markDist)
P2Add(pos, step)
}
}
const insetW = W * 0.1;
const insetH = H * 0.1;
const axisText = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
clear();
drawAxis(
P2(insetW, H - insetH), P2(insetW, insetH), paths.markLeft,
styles.markStyle,
axisText,
{...styles.textLeft, ...styles.markTextStyle},
-18
);
drawAxis(
P2(insetW, H - insetH), P2(W - insetW, H - insetH), paths.markUp,
styles.markStyle,
axisText,
{...styles.textHang, ...styles.markTextStyle},
6
);
canvas {
border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>