Для наилучшего качества всегда рендеринг на холст с помощью обратного вызова requestAnimationFrame
.Рендеринг с использованием таймеров может привести к мерцанию и / или сдвигу анимации.
setTimeout
или setInterval
подавляются большинством браузеров, когда страница не находится в фокусе или не видна.Также обратные вызовы не всегда вовремя
Если время важно для 1/60 секунды, используйте performance.now
и аргумент времени передается обратному вызову requestAnimationFrame
.Вы никогда не будете точно вовремя, так как анимация отображается только каждые 1/60 секунды (16.66666 ... мс), поэтому нарисуйте следующий кадр анимации как можно скорее после требуемого времени (см. Пример)
Из информации, которую вы дали в этом вопросе, я не смог понять, как выглядит ваша анимация, поэтому я придумал что-то в качестве примера.
Пример
- При загрузке используется обещание, а не обратный вызовmedia
- К изображению добавлено свойство, представляющее положение подизображений (спрайтов).Функция
drawSprite
использует imageName
, spriteIdx
и locationName
для отрисовки вспомогательного изображения в месте на холсте. - Основным циклом рендеринга является функция
renderLoop
, которая ожидает домультимедиа загружается и затем использует timing
для анимации. - Массив
timing
содержит объект для каждого события анимации.У объекта есть временное смещение события, функция, которая будет вызвана для этого события, и аргументы, переданные функции. - Последний объект синхронизации в этом примере просто сбрасывает время начала, чтобы повторить анимацию.
Обратите внимание, что этот пример не проверяет, останавливает ли браузер анимацию и будет ли циклически перебирать все анимации, чтобы их догнать.
requestAnimationFrame(renderLoop);
canvas.width = 64;
canvas.height = 16;
const ctx = canvas.getContext("2d");
var mediaLoaded = false;
const images = {};
var currentStage = 0;
var startTime; // do not assign a value to this here or the animation may not start
loadMedia({
leds: {
src: "https://i.stack.imgur.com/g4Iev.png",
sprites: [
{x: 0, y: 0, w: 16, h: 16}, // idx 0 red off
{x: 16, y: 0, w: 16, h: 16}, // idx 1 red on
{x: 0, y: 16, w: 16, h: 16}, // idx 2 orange off
{x: 16, y: 16, w: 16, h: 16}, // idx 3 orange on
{x: 0, y: 32, w: 16, h: 16}, // idx 4 green off
{x: 16, y: 32, w: 16, h: 16}, // idx 5 green on
{x: 0, y: 48, w: 16, h: 16}, // idx 6 cyan off
{x: 16, y: 48, w: 16, h: 16}, // idx 7 cyan on
]
},
}, images)
.then(() => mediaLoaded = true);
const renderLocations = {
a: {x: 0, y: 0, w: 16, h: 16},
b: {x: 16, y: 0, w: 16, h: 16},
c: {x: 32, y: 0, w: 16, h: 16},
d: {x: 48, y: 0, w: 16, h: 16},
};
function loadMedia(imageSources, images = {}) {
return new Promise(allLoaded => {
var count = 0;
for (const [name, desc] of Object.entries(imageSources)) {
const image = new Image;
image.src = desc.src;
image.addEventListener("load",() => {
images[name] = image;
if (desc.sprites) { image.sprites = desc.sprites }
count --;
if (!count) { allLoaded(images) }
});
count ++;
}
});
}
function drawSprite(imageName, spriteIdx, locName) {
const loc = renderLocations[locName];
const spr = images[imageName].sprites[spriteIdx];
ctx.drawImage(images[imageName], spr.x, spr.y, spr.w, spr.h, loc.x, loc.y, loc.w, loc.h);
}
function drawLeds(sprites) {
for(const spr of sprites) { drawSprite(...spr) }
}
function resetAnimation() {
currentStage = 0;
startTime += 4500;
}
const timing = [
{time: 0, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 4, "c"], ["leds", 6, "d"]]]},
{time: 500, func: drawLeds, args: [[["leds", 1, "a"]]]},
{time: 1500, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 3, "b"]]]},
{time: 2000, func: drawLeds, args: [[["leds", 1, "a"]]]},
{time: 3000, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 5, "c"], ["leds", 7, "d"]]]},
{time: 3250, func: drawLeds, args: [[["leds", 1, "d"]]]},
{time: 3500, func: drawLeds, args: [[["leds", 3, "d"]]]},
{time: 3750, func: drawLeds, args: [[["leds", 5, "d"]]]},
{time: 4000, func: drawLeds, args: [[["leds", 7, "d"]]]},
{time: 4250, func: drawLeds, args: [[["leds", 1, "d"]]]},
{time: 4500 - 17, func: resetAnimation, args: []},
];
function renderLoop(time) {
if (mediaLoaded) {
if (startTime === undefined) { startTime = time }
var offsetTime = time - startTime;
const stage = timing[currentStage];
if(offsetTime > stage.time) {
currentStage++;
stage.func(...stage.args);
}
}
requestAnimationFrame(renderLoop);
}
canvas {
border:1px solid black;
}
<canvas id="canvas"></canvas>
Изображение, использованное в примере
![Color LEDs sprites On and Off](https://i.stack.imgur.com/g4Iev.png)