#html #animation #canvas #bezier
Вопрос:
Я хочу создать анимацию с изображением холста, следующего за полукругом. Сначала я попробовал с холстом arc
, но нашел, что с ним все проще bezier curve
.
Но теперь я столкнулся с проблемой, потому что, поскольку он не находится на круге, я не могу найти способ заставить его вращаться в соответствии с его положением, как указатель часов. До сих пор это мой код.
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
var statue = new Image();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';
function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
var x =
Math.pow(1 - percent, 2) * startPt.x
2 * (1 - percent) * percent * controlPt.x
Math.pow(percent, 2) * endPt.x;
var y =
Math.pow(1 - percent, 2) * startPt.y
2 * (1 - percent) * percent * controlPt.y
Math.pow(percent, 2) * endPt.y;
return { x: x, y: y };
}
const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };
var percent = 0;
statue.addEventListener('load', () => {
animate();
});
function animate() {
//console.log(percent);
ctx.clearRect(0, 0, c.width, c.height);
percent > 1 ? (percent = 0) : (percent = 0.003);
var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);
ctx.drawImage(
statue,
0,
0,
statue.width,
statue.height,
point.x - 50,
point.y - 50,
100,
100
);
//ctx.fillRect(point.x, point.y, 10, 10);
requestAnimationFrame(animate);
}
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="600" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
</script>
</body>
</html>
Как правильно это сделать?
Комментарии:
1. Можете ли вы объяснить, почему кривая Безье проще, потому что, как человек, который немного разбирается в кривых Безье: дуга окружности намного проще, чем кривая Безье.
2. Это потому, что я мог получить желаемую форму, которую я хотел ( слегка сплющенный полукруг), и положение изображения в определенное время с помощью простой функции без использования тригонометрии .
3. за счет нелинейного движения по траектории и исчисления вместо тригонометрии, что является произвольной разницей (квадратичное вычисление сложнее и дороже, чем математика дуги)
Ответ №1:
Вы можете использовать ту же функцию, чтобы получить другую точку немного раньше текущей на кривой, а затем использовать Math.atan2
ее для определения угла между двумя точками.
Затем вам нужно будет использовать ctx.translate()
и ctx.rotate()
изменить матрицу преобразования вместо установки позиции в .drawImage()
вызове. ( .setTransform()
Вызов в начале метода анимации сбрасывает матрицу для каждого кадра.)
Я также добавил сюда эффект «луковой шкурки», чтобы движение было лучше видно.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var statue = new Image();
statue.src = "https://i.ibb.co/3TvCH8n/liberty.png";
function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
var x =
Math.pow(1 - percent, 2) * startPt.x
2 * (1 - percent) * percent * controlPt.x
Math.pow(percent, 2) * endPt.x;
var y =
Math.pow(1 - percent, 2) * startPt.y
2 * (1 - percent) * percent * controlPt.y
Math.pow(percent, 2) * endPt.y;
return { x: x, y: y };
}
const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };
var percent = 0;
statue.addEventListener("load", () => {
ctx.clearRect(0, 0, c.width, c.height);
animate();
});
function animate() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
// "Onion skin" effect so the last frame is slightly retained to better show the motion.
ctx.fillStyle = "rgba(255,255,255,0.1)";
ctx.fillRect(0, 0, c.width, c.height);
percent = (percent 0.003) % 1;
var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);
var lastPoint = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent - 0.003);
var angle = Math.atan2(lastPoint.y - point.y, lastPoint.x - point.x);
// Debug pointer line
ctx.beginPath();
ctx.moveTo(point.x, point.y);
ctx.lineTo(point.x Math.cos(angle) * 50, point.y Math.sin(angle) * 50);
ctx.stroke();
// Actual drawing
ctx.translate(point.x, point.y);
ctx.rotate(angle);
ctx.drawImage(statue, 0, 0, statue.width, statue.height, -50, -50, 100, 100);
requestAnimationFrame(animate);
}
<canvas id="myCanvas" width="600" height="400" style="border:1px solid #d3d3d3;">
Комментарии:
1. Ничего не вычисляя и просто позволяя холсту выполнять всю тяжелую работу, рассказывая ему, как перевести()/повернуть (), еще приятнее: математика вообще не требуется.
Ответ №2:
В любом случае просто используйте круг, это намного проще, но вместо того, чтобы пытаться разместить объекты в координатах, сделайте это наоборот: установите систему координат на правильное смещение и угол, чтобы затем вы могли нарисовать то же самое в той же координате.
Мы наблюдаем, что мы имеем дело с круговым путем, который имеет (300 600) в качестве начала координат, радиусом 500 и начальным углом (в радианах) 0,9272951769 и конечным углом (в радианах) 2,21429922, поэтому мы можем сдвинуть нашу систему координат так, чтобы (0,0) было «действительно» (300 600), а затем, когда мы поворачиваем систему координат вокруг этого нового (0,0) на любой угол, все, что нам нужно сделать, это нарисовать объекты в (радиус, 0)
Поскольку система координат вращается за нас, нам не нужно на самом деле вычислять какие-либо точки. Мы уже знаем, где мы хотим, чтобы все было: на расстоянии radius
от источника.
cvs.width = 600;
cvs.height = 200;
const ctx = cvs.getContext('2d');
const statue = new Image();
statue.onload = () => animate();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';
const sWidth = sHeight = 80;
// circle values matching your path:
const origin = {x: 300, y: 600 };
const radius = 500;
const start = 0.9272951769;
const end = 2.21429922;
// animation values
let step = 0;
const totalSteps = 120;
const stepSize = (end - start)/totalSteps;
// And our drawing function
function animate() {
ctx.resetTransform();
ctx.clearRect(0, 0, cvs.width, cvs.height);
if (step === totalSteps) step = 0;
const angle = start step * stepSize;
// first, change the coordinate system so that we don't need
// to compute *anything* to draw it in the right place:
ctx.translate(origin.x, origin.y);
ctx.rotate(-angle);
// Then we draw the debug line:
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(radius, 0);
ctx.stroke();
// And then we draw the image. However, the image is upright
// when (radius,0) lines up with the x-axis, which means it's
// actually going to look rotated compared to our line. So:
// again, we update the coordinate system to do the work for us.
// we update it so that (radius,0) becomes (0,0), we then rotate
// it a quarter turn, and then we draw our image at (0,0).
ctx.translate(radius, 0);
ctx.rotate(Math.PI/2);
// of course, (0,0) is the image's top-left corner, so if we
// want to center it, we can do one more translation:
ctx.translate(-sWidth/2, -sHeight/2);
ctx.drawImage(statue, 0, 0, sWidth, sHeight);
// and move on to the next frame
requestAnimationFrame(animate);
}
<canvas id="cvs"></canvas>
Таким образом, мы не пишем никакого кода для вычисления «где нам нужно рисовать, с какой ориентацией», вместо этого мы используем только фиксированные точки и позволяем контексту canvas2d позаботиться обо всех переводах/поворотах.
И небольшой известный факт: все браузеры делают HTML-элементы с id
атрибутом доступными для JS, используя этот идентификатор в качестве имени переменной. Итак , в данном случае у нас есть a <canvas id="cvs">
, что означает, что на стороне JS есть переменная, называемая cvs
, которая указывает на наш элемент canvas.
Комментарии:
1. Хм, IIRC квадратичный путь Безье не может точно представлять круг?
2. Правильно, но первоначальный пост начинался с того, что они хотели полукруг, но затем использовали квадратичный Безье «потому что это было проще», чего просто нет, вместо этого все намного сложнее =)
3. Я подумал, что у ОП тоже была какая-то другая причина переключиться на кривую Безье. Но да, проследить дугу окружности было бы очень просто с небольшим количеством тригонометрии…
4. Я закончил использовать это решение после того, как закончил его полностью понимать (стоило мне намного больше, чем решение Безье…). Но поскольку @AKX ответил на мою проблему, я все равно должен оставить ее как «принятый ответ».
5. возможно, вы захотите изменить принятый ответ, но оба ответа основаны на одной и той же концепции: измените систему координат и нарисуйте вещи «на месте», а не сохраняйте систему координат на месте и меняйте место, где вещи рисуются =)