<canvas id="canv" width="800" height="600"></canvas>
const baseURL = "https://yadrenakopot.ru/assets/images/zoom/";
const frames = 31;
const links = Array.from(
  { length: frames },
  (_, i) => `${baseURL}${(i + 1).toString().padStart(2, "0")}.jpg`
);

function loadImg(src) {
  return new Promise(resolve => {
    const img = new Image();
    img.addEventListener('load', () => resolve(img));
    //img.crossOrigin = 'Anonymous';
    img.src = src;
  });
}

Promise.all(links.map(url => loadImg(url)))
  .then(images => play(images));

function play(images) {
  const width = 800;
  const height = 600;
  const zoom = 2.015;
  const duration = 60000;
  const canvas = document.querySelector("#canv");
  const ctx = canvas.getContext("2d");
  ctx.translate(width / 2, height / 2);
  
  function clear() {
    ctx.clearRect(-ctx.canvas.width / 2, -ctx.canvas.height / 2, ctx.canvas.width, ctx.canvas.height);
  }
  
  let begin = 0;
  function loop(now) {
    begin = begin || now;
    const elapsed = now - begin;
    const progress = (elapsed / duration) % 1;
    const nf = Math.floor(progress * (frames - 1));
    const pf = (progress * (frames - 1)) % 1;
    
    const scale = 1 + (zoom - 1) * pf;
    const nextScale = 1/zoom + (1 - 1/zoom) * pf;
    const image1 = images[nf];
    const image2 = images[(nf + 1) % frames];
    
    clear();
 
    ctx.drawImage(image1, 0, 0, width, height, -width/2 * scale, -height/2 * scale, width * scale, height * scale);
    
    ctx.globalAlpha = pf;
    ctx.drawImage(image2, 0, 0, width, height, -width/2 * nextScale, -height/2 * nextScale, width * nextScale, height * nextScale);
    ctx.globalAlpha = 1;

    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.