Поиск…
Простая анимация с 2D-контекстом и requestAnimationFrame
В этом примере будет показано, как создать простую анимацию, используя холст и 2D-контекст. Предполагается, что вы знаете, как создать и добавить холст в DOM и получить контекст
// this example assumes ctx and canvas have been created
const textToDisplay = "This is an example that uses the canvas to animate some text.";
const textStyle = "white";
const BGStyle = "black"; // background style
const textSpeed = 0.2; // in pixels per millisecond
const textHorMargin = 8; // have the text a little outside the canvas
ctx.font = Math.floor(canvas.height * 0.8) + "px arial"; // size the font to 80% of canvas height
var textWidth = ctx.measureText(textToDisplay).width; // get the text width
var totalTextSize = (canvas.width + textHorMargin * 2 + textWidth);
ctx.textBaseline = "middle"; // not put the text in the vertical center
ctx.textAlign = "left"; // align to the left
var textX = canvas.width + 8; // start with the text off screen to the right
var textOffset = 0; // how far the text has moved
var startTime;
// this function is call once a frame which is approx 16.66 ms (60fps)
function update(time){ // time is passed by requestAnimationFrame
if(startTime === undefined){ // get a reference for the start time if this is the first frame
startTime = time;
}
ctx.fillStyle = BGStyle;
ctx.fillRect(0, 0, canvas.width, canvas.height); // clear the canvas by drawing over it
textOffset = ((time - startTime) * textSpeed) % (totalTextSize); // move the text left
ctx.fillStyle = textStyle; // set the text style
ctx.fillText(textToDisplay, textX - textOffset, canvas.height / 2); // render the text
requestAnimationFrame(update);// all done request the next frame
}
requestAnimationFrame(update);// to start request the first frame
Демонстрация этого примера в jsfiddle
Анимация с заданным интервалом (добавьте новый прямоугольник каждые 1 секунду)
Этот пример добавляет новый прямоугольник к холсту каждые 1 секунду (== интервал 1 секунда)
Аннотированный код:
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// animation interval variables
var nextTime=0; // the next animation begins at "nextTime"
var duration=1000; // run animation every 1000ms
var x=20; // the X where the next rect is drawn
// start the animation
requestAnimationFrame(animate);
function animate(currentTime){
// wait for nextTime to occur
if(currentTime<nextTime){
// request another loop of animation
requestAnimationFrame(animate);
// time hasn't elapsed so just return
return;
}
// set nextTime
nextTime=currentTime+duration;
// add another rectangle every 1000ms
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(x,30,30,30);
// update X position for next rectangle
x+=30;
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Анимация в указанное время (анимированные часы)
Этот пример оживляет часы, показывающие секунды как заполненный клин
Аннотированный код:
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// canvas styling for the clock
ctx.strokeStyle='lightgray';
ctx.fillStyle='skyblue';
ctx.lineWidth=5;
// cache often used values
var PI=Math.PI;
var fullCircle=PI*2;
var sa=-PI/2; // == the 12 o'clock angle in context.arc
// start the animation
requestAnimationFrame(animate);
function animate(currentTime){
// get the current seconds value from the system clock
var date=new Date();
var seconds=date.getSeconds();
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw a full circle (== the clock face);
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arc(100,100,75,0,fullCircle);
ctx.stroke();
// draw a wedge representing the current seconds value
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arc(100,100,75,sa,sa+fullCircle*seconds/60);
ctx.fill();
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Использовать requestAnimationFrame () NOT setInterval () для анимационных циклов
requestAnimationFrame
похож на setInterval, но имеет следующие важные улучшения:
Код анимации синхронизируется с обновлением дисплея для повышения эффективности. Код clear + redraw запланирован, но не сразу выполняется. Браузер выполнит код clear + redraw только тогда, когда дисплей готов к обновлению. Эта синхронизация с циклом обновления увеличивает производительность вашей анимации, предоставляя вашему коду самое доступное время для его завершения.
Каждый цикл всегда завершается до запуска другого цикла. Это предотвращает «разрывы», когда пользователь видит неполную версию чертежа. Глаз особенно замечает разрывание и отвлекается при разрыве. Поэтому предотвращение разрыва делает вашу анимацию более гладкой и последовательной.
- Анимация автоматически останавливается, когда пользователь переключается на другую вкладку браузера. Это экономит электроэнергию на мобильных устройствах, потому что устройство не тратит энергию, вычисляя анимацию, которую пользователь в настоящее время не видит.
Дисплеи устройств будут обновляться примерно 60 раз в секунду, поэтому requestAnimationFrame может непрерывно перерисовывать со скоростью около 60 кадров в секунду. Глаз видит движение со скоростью 20-30 кадров в секунду, поэтому requestAnimationFrame может легко создать иллюзию движения.
Обратите внимание, что requestAnimationFrame вызывается в конце каждого цикла animateCircle. Это потому, что каждый запрос requestAnimatonFrameonly запрашивает одно выполнение функции анимации.
Пример: простой `requestAnimationFrame
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// start the animation
requestAnimationFrame(animate);
function animate(currentTime){
// draw a full randomly circle
var x=Math.random()*canvas.width;
var y=Math.random()*canvas.height;
var radius=10+Math.random()*15;
ctx.beginPath();
ctx.arc(x,y,radius,0,Math.PI*2);
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fill();
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Чтобы проиллюстрировать преимущества requestAnimationFrame, этот вопрос stackoverflow имеет живую демонстрацию
Анимация изображения через холст
Этот пример загружает, анимирует и создает изображение через холст
Важное замечание! Убедитесь, что время загрузки изображения полностью загружено с помощью image.onload
.
Аннотированный код
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// animation related variables
var minX=20; // Keep the image animating
var maxX=250; // between minX & maxX
var x=minX; // The current X-coordinate
var speedX=1; // The image will move at 1px per loop
var direction=1; // The image direction: 1==righward, -1==leftward
var y=20; // The Y-coordinate
// Load a new image
// IMPORTANT!!! You must give the image time to load by using img.onload!
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/sun.png";
function start(){
// the image is fully loaded sostart animating
requestAnimationFrame(animate);
}
function animate(time){
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw
ctx.drawImage(img,x,y);
// update
x += speedX * direction;
// keep "x" inside min & max
if(x<minX){ x=minX; direction*=-1; }
if(x>maxX){ x=maxX; direction*=-1; }
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Не рисуйте анимации в обработчиках событий (простое приложение эскиза)
Во время mousemove
вы залиты 30 событиями мыши в секунду. Возможно, вы не сможете перерисовывать свои рисунки 30 раз в секунду. Даже если это возможно, вы, вероятно, тратите впустую вычислительную мощность, рисуя, когда браузер не готов к рисованию (впустую == через циклы обновления дисплея).
Поэтому имеет смысл отделять ваши пользовательские события ввода (например, mousemove) от рисования ваших анимаций.
В обработчиках событий сохраняйте все переменные событий, которые контролируют расположение чертежей на холсте. Но на самом деле ничего не рисуйте.
В цикле
requestAnimationFrame
визуализируйте все чертежи на холст с помощью сохраненной информации.
Не рисуя обработчики событий, вы не заставляете Canvas пытаться обновить сложные рисунки при скоростях событий мыши.
Выполняя весь чертеж в requestAnimationFrame
вы получаете все преимущества, описанные здесь. Используйте «requestanimationFrame», а не «setInterval» для циклов анимации .
Аннотированный код:
<!doctype html>
<html>
<head>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
function log(){console.log.apply(console,arguments);}
// canvas variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// set canvas styling
ctx.strokeStyle='skyblue';
ctx.lineJoint='round';
ctx.lineCap='round';
ctx.lineWidth=6;
// handle windows scrolling & resizing
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
// vars to save points created during mousemove handling
var points=[];
var lastLength=0;
// start the animation loop
requestAnimationFrame(draw);
canvas.onmousemove=function(e){handleMouseMove(e);}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// save the mouse position in the points[] array
// but don't draw anything
points.push({x:mouseX,y:mouseY});
}
function draw(){
// No additional points? Request another frame an return
var length=points.length;
if(length==lastLength){requestAnimationFrame(draw);return;}
// draw the additional points
var point=points[lastLength];
ctx.beginPath();
ctx.moveTo(point.x,point.y)
for(var i=lastLength;i<length;i++){
point=points[i];
ctx.lineTo(point.x,point.y);
}
ctx.stroke();
// request another animation loop
requestAnimationFrame(draw);
}
}); // end window.onload
</script>
</head>
<body>
<h4>Move mouse over Canvas to sketch</h4>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Ослабление с помощью уравнений Роберта Пеннерса
Ослабление приводит к тому, что некоторые переменные изменяются неравномерно по длительности .
«переменная» должна быть выражена как число и может представлять собой замечательное множество вещей:
- X-координата,
- ширина прямоугольника,
- угол поворота,
- красный компонент цвета R, G, B.
- все, что может быть выражено как число.
«продолжительность» должна быть выражена как число, а также может быть различными:
- Период времени,
- расстояние, которое нужно проехать,
- количество циклов анимации, которые должны быть выполнены,
- все, что может быть выражено как
«неравномерно» означает, что переменная переходит от начала до конца значения неравномерно:
- быстрее в начале и медленнее в конце - или наоборот,
- перевыполняет окончание, но возвращается к финалу по мере того, как длительность заканчивается,
- неоднократно продвигается / отступает эластично в течение продолжительности,
- «отскакивает» от финала, когда он отдыхает, когда заканчивается продолжительность.
Attribution: Роберт Пеннер создал «золотой стандарт» функций ослабления.
Cite: https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
// t: elapsed time inside duration (currentTime-startTime),
// b: beginning value,
// c: total change from beginning value (endingValue-startingValue),
// d: total duration
var Easings={
easeInQuad: function (t, b, c, d) {
return c*(t/=d)*t + b;
},
easeOutQuad: function (t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
},
easeInOutQuad: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
easeInCubic: function (t, b, c, d) {
return c*(t/=d)*t*t + b;
},
easeOutCubic: function (t, b, c, d) {
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOutCubic: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
},
easeInQuart: function (t, b, c, d) {
return c*(t/=d)*t*t*t + b;
},
easeOutQuart: function (t, b, c, d) {
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOutQuart: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
},
easeInQuint: function (t, b, c, d) {
return c*(t/=d)*t*t*t*t + b;
},
easeOutQuint: function (t, b, c, d) {
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOutQuint: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
},
easeInSine: function (t, b, c, d) {
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOutSine: function (t, b, c, d) {
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOutSine: function (t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
},
easeInExpo: function (t, b, c, d) {
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOutExpo: function (t, b, c, d) {
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOutExpo: function (t, b, c, d) {
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
},
easeInCirc: function (t, b, c, d) {
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOutCirc: function (t, b, c, d) {
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOutCirc: function (t, b, c, d) {
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
},
easeInElastic: function (t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOutElastic: function (t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
},
easeInOutElastic: function (t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
},
easeInBack: function (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOutBack: function (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOutBack: function (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
},
easeInBounce: function (t, b, c, d) {
return c - Easings.easeOutBounce (d-t, 0, c, d) + b;
},
easeOutBounce: function (t, b, c, d) {
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOutBounce: function (t, b, c, d) {
if (t < d/2) return Easings.easeInBounce (t*2, 0, c, d) * .5 + b;
return Easings.easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b;
},
};
Пример использования:
// include the Easings object from above
var Easings = ...
// Demo
var startTime;
var beginningValue=50; // beginning x-coordinate
var endingValue=450; // ending x-coordinate
var totalChange=endingValue-beginningValue;
var totalDuration=3000; // ms
var keys=Object.keys(Easings);
ctx.textBaseline='middle';
requestAnimationFrame(animate);
function animate(time){
var PI2=Math.PI*2;
if(!startTime){startTime=time;}
var elapsedTime=Math.min(time-startTime,totalDuration);
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
for(var y=0;y<keys.length;y++){
var key=keys[y];
var easing=Easings[key];
var easedX=easing(
elapsedTime,beginningValue,totalChange,totalDuration);
if(easedX>endingValue){easedX=endingValue;}
ctx.moveTo(easedX,y*15);
ctx.arc(easedX,y*15+10,5,0,PI2);
ctx.fillText(key,460,y*15+10-1);
}
ctx.fill();
if(time<startTime+totalDuration){
requestAnimationFrame(animate);
}
}
Установите частоту кадров с помощью requestAnimationFrame
Использование requestAnimationFrame может в некоторых системах обновляться со скоростью более кадров в секунду, чем 60 кадров в секунду. 60fps - это ставка по умолчанию, если рендеринг может идти в ногу. Некоторые системы будут работать со скоростью 120 кадров в секунду, возможно, больше.
Если вы используете следующий метод, вы должны использовать только частоты кадров, которые являются целыми делениями 60, так что (60 / FRAMES_PER_SECOND) % 1 === 0
true
или вы получите несогласованные частоты кадров.
const FRAMES_PER_SECOND = 30; // Valid values are 60,30,20,15,10...
// set the mim time to render the next frame
const FRAME_MIN_TIME = (1000/60) * (60 / FRAMES_PER_SECOND) - (1000/60) * 0.5;
var lastFrameTime = 0; // the last frame time
function update(time){
if(time-lastFrameTime < FRAME_MIN_TIME){ //skip the frame if the call is too early
requestAnimationFrame(update);
return; // return as there is nothing to do
}
lastFrameTime = time; // remember the time of the rendered frame
// render the frame
requestAnimationFrame(update); // get next farme
}
requestAnimationFrame(update); // start animation
Анимация от [x0, y0] до [x1, y1]
Используйте векторы для вычисления инкрементных [x, y] с [startX, startY] до [endX, endY]
// dx is the total distance to move in the X direction
var dx = endX - startX;
// dy is the total distance to move in the Y direction
var dy = endY - startY;
// use a pct (percentage) to travel the total distances
// start at 0% which == the starting point
// end at 100% which == then ending point
var pct=0;
// use dx & dy to calculate where the current [x,y] is at a given pct
var x = startX + dx * pct/100;
var y = startY + dx * pct/100;
Пример кода:
// canvas vars
var canvas=document.createElement("canvas");
document.body.appendChild(canvas);
canvas.style.border='1px solid red';
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// canvas styles
ctx.strokeStyle='skyblue';
ctx.fillStyle='blue';
// animating vars
var pct=101;
var startX=20;
var startY=50;
var endX=225;
var endY=100;
var dx=endX-startX;
var dy=endY-startY;
// start animation loop running
requestAnimationFrame(animate);
// listen for mouse events
window.onmousedown=(function(e){handleMouseDown(e);});
window.onmouseup=(function(e){handleMouseUp(e);});
// constantly running loop
// will animate dot from startX,startY to endX,endY
function animate(time){
// demo: rerun animation
if(++pct>100){pct=0;}
// update
x=startX+dx*pct/100;
y=startY+dy*pct/100;
// draw
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(startX,startY);
ctx.lineTo(endX,endY);
ctx.stroke();
ctx.beginPath();
ctx.arc(x,y,5,0,Math.PI*2);
ctx.fill()
// request another animation loop
requestAnimationFrame(animate);
}