HTML画布在Safari上无法显示

6

我正在使用下面Codepen中的代码创建一个全宽度画布,模拟在滚动时图像序列的视频播放。

它在Chrome中运行良好,但我无法使其在Safari上运行-它看起来像是空白的。如果我检查该元素,则在Safari中似乎不存在。

是否已知存在此问题或可在代码中修复的内容?

Codepen: https://codepen.io/SalmanShaikh/pen/MWeNBLL

const appleSequenceImages = [];
//number of images 131; fill the array
//see the squence obj below for image path.
for (let i = 0; i <= 374; i++) {
  console.log(`${`${i}`.slice(-4)}.jpg`);
  appleSequenceImages.push(`${`${i}`.slice(-4)}.jpg`);
}


const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

class EventEmitter {
  listeners = {}
  addListener(eventName, fn) {
    this.listeners[eventName] = this.listeners[eventName] || [];
    this.listeners[eventName].push(fn);
    return this;
  }
  on(eventName, fn) {
    return this.addListener(eventName, fn);
  }
  once(eventName, fn) {
    this.listeners[eventName] = this.listeners[eventName] || [];
    const onceWrapper = () => {
      fn();
      this.off(eventName, onceWrapper);
    }
    this.listeners[eventName].push(onceWrapper);
    return this;
  }
  off(eventName, fn) {
    return this.removeListener(eventName, fn);
  }
  removeListener(eventName, fn) {
    let lis = this.listeners[eventName];
    if (!lis) return this;
    for (let i = lis.length; i > 0; i--) {
      if (lis[i] === fn) {
        lis.splice(i, 1);
        break;
      }
    }
    return this;
  }
  emit(eventName, ...args) {
    let fns = this.listeners[eventName];
    if (!fns) return false;
    fns.forEach((f) => {
      f(...args);
    });
    return true;
  }
  listenerCount(eventName) {
    let fns = this.listeners[eventName] || [];
    return fns.length;
  }
  rawListeners(eventName) {
    return this.listeners[eventName];
  }
}
class Canvas {
  constructor(e) {
    this.images = e.images;
    this.container = e.container;
    this.cover = e.cover;
    this.displayIndex = 0;
  }

  setup() {
    this.canvas = document.createElement("canvas");
    this.container.appendChild(this.canvas);
    this.ctx = this.canvas.getContext('2d')

    window.addEventListener('resize', () => this.resize());
    this.resize();
  }

  renderIndex(e) {
    if (this.images[e]) {
      return this.drawImage(e);
    }
    // Find closest loaded image
    for (var t = Number.MAX_SAFE_INTEGER, r = e; r >= 0; r--)
      if (this.images[r]) {
        t = r;
        break
      }
    for (var n = Number.MAX_SAFE_INTEGER, i = e, o = this.images.length; i < o; i++)
      if (this.images[i]) {
        n = i;
        break
      }
    this.images[t] ? this.drawImage(t) : this.images[n] && this.drawImage(n)
  }

  drawImage(e) {
    this.displayIndex = e,
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    const x = Math.floor((this.canvas.width - this.images[this.displayIndex].naturalWidth) / 2);
    const y = Math.floor((this.canvas.height - this.images[this.displayIndex].naturalHeight) / 2);
    if (this.cover) {

      this.drawImageCover(this.ctx, this.images[this.displayIndex]);
    } else {
      this.ctx.drawImage(this.images[this.displayIndex], x, y);
    }
  }

  resize() {
    const w = this.container.clientWidth;
    const h = this.container.clientHeight;
    this.canvas.style.height = `${h}px`;
    this.canvas.style.width = `${w}px`;
    this.canvas.height = h;
    this.canvas.width = w;

    this.renderIndex(this.displayIndex);
  }

  /**
   * Main code by Ken Fyrstenberg Nilsen, modified by me.
   *
   * drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
   *
   * If image and context are only arguments rectangle will equal canvas
   */
  drawImageCover(ctx, img, x, y, w, h, offsetX, offsetY) {

    if (arguments.length === 2) {
      x = y = 0;
      w = ctx.canvas.width;
      h = ctx.canvas.height;
    }

    // default offset is center
    offsetX = typeof offsetX === "number" ? offsetX : 0.5;
    offsetY = typeof offsetY === "number" ? offsetY : 0.5;

    // keep bounds [0.0, 1.0]
    if (offsetX < 0) offsetX = 0;
    if (offsetY < 0) offsetY = 0;
    if (offsetX > 1) offsetX = 1;
    if (offsetY > 1) offsetY = 1;
    //added new prop.width and height for precise movement
    var iw = img.width,
      ih = img.height,
      r = Math.min(w / iw, h / ih),
      nw = iw * r, // new prop. width
      nh = ih * r, // new prop. height
      cx, cy, cw, ch, ar = 1;

    // decide which gap to fill
    if (nw < w) ar = w / nw;
    if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
    nw *= ar;
    nh *= ar;

    // calc source rectangle
    cw = iw / (nw / w);
    ch = ih / (nh / h);

    cx = (iw - cw) * offsetX;
    cy = (ih - ch) * offsetY;

    // make sure source rectangle is valid
    if (cx < 0) cx = 0;
    if (cy < 0) cy = 0;
    if (cw > iw) cw = iw;
    if (ch > ih) ch = ih;

    // fill image in dest. rectangle
    ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
  }
}
class ImgLoader extends EventEmitter {
  constructor(opts) {
    super();
    this.images = opts.imgsRef;
    this.imageNames = opts.images;
    this.imagesRoot = opts.imagesRoot;
    this.sequenceLength = opts.images.length;
    this.priorityFranes = opts.priorityFrames;
    this.complete = false;
    this.loadIndex = 0;

    this.priorityQueue = this.createPriorityQueue();
    this.loadingQueue = this.createLoadingQueue();

    this.loadNextImage();
  }

  loadImage(e) {
    if (this.images[e]) {
      return this.loadNextImage();
    }
    const onLoad = () => {
      img.removeEventListener('load', onLoad);
      this.images[e] = img;

      if (e === 0) {
        this.emit('FIRST_IMAGE_LOADED');
      }
      this.loadNextImage();
    }
    const img = new Image;
    img.addEventListener('load', onLoad);
    img.src = (this.imagesRoot ? this.imagesRoot : '') + this.imageNames[e];
  }

  loadNextImage() {
    if (this.priorityQueue.length) {
      this.loadImage(this.priorityQueue.shift());
      if (!this.priorityQueue.length) {
        this.emit('PRIORITY_IMAGES_LOADED');
      }
    } else if (this.loadingQueue.length) {
      this.loadImage(this.loadingQueue.shift())
    } else {
      this.complete = true;
      this.emit('IMAGES_LOADED');
    }
  }

  createPriorityQueue() {
    const p = this.priorityFrames || [];
    if (!p.length) {
      p.push(0);
      p.push(Math.round(this.sequenceLength / 2));
      p.push(this.sequenceLength - 1);
    }
    return p;
  }

  createLoadingQueue() {
    return this.imageNames.map((s, i) => i).sort((e, n) => {
      return Math.abs(e - this.sequenceLength / 2) - Math.abs(n - this.sequenceLength / 2)
    });
  }
}
class ScrollSequence {
  constructor(opts) {
    this.opts = {
      container: 'body',
      starts: 'out',
      ends: 'out',
      imagesRoot: '',
      cover: false,
      ...opts
    }
    this.container = typeof opts.container === 'object' ?
      opts.container :
      document.querySelector(opts.container);

    this.scrollWith = !opts.scrollWith ?
      this.container :
      typeof opts.scrollWith === 'object' ?
      opts.scrollWith :
      document.querySelector(opts.scrollWith);

    this.images = Array(opts.images.length);
    this.imagesToLoad = opts.images;
    this.priorityFrames = opts.priorityFrames;

    this.loader = new ImgLoader({
      imgsRef: this.images,
      images: this.imagesToLoad,
      imagesRoot: this.opts.imagesRoot,
      priorityFrames: this.priorityFrames
    });

    this.canvas = new Canvas({
      container: this.container,
      images: this.images,
      cover: this.opts.cover
    });

    this.init();
  }

  init() {
    this.canvas.setup();
    this.loader.once('FIRST_IMAGE_LOADED', () => {
      this.canvas.renderIndex(0);
    })
    this.loader.once('PRIORITY_IMAGES_LOADED', () => {
      window.addEventListener('scroll', () => this.changeOnWindowScroll());
    })
    this.loader.once('IMAGES_LOADED', () => {
      console.log('Sequence Loaded');
    })
  }

  changeOnWindowScroll() {
    const step = 100 / (this.images.length - 1);
    const mapToIndex = Math.floor(this.percentScrolled / step);
    requestAnimationFrame(() => this.canvas.renderIndex(mapToIndex));
  }

  get percentScrolled() {
    const {
      starts,
      ends
    } = this.opts;
    const el = this.scrollWith;
    const doc = document.documentElement;
    const clientOffsety = doc.scrollTop || window.pageYOffset;
    const elementHeight = el.clientHeight || el.offsetHeight;
    const clientHeight = doc.clientHeight;
    let target = el;
    let offsetY = 0;
    do {
      offsetY += target.offsetTop;
      target = target.offsetParent;
    } while (target && target !== window);

    let u = (clientOffsety - offsetY);
    let d = (elementHeight + clientHeight)

    if (starts === 'out') u += clientHeight;
    if (ends === 'in') d -= clientHeight;
    if (starts == 'in') d -= clientHeight;

    const value = u / d * 100;
    return value > 100 ? 100 : value < 0 ? 0 : value;
  }
}

const appleSequence = new ScrollSequence({
container: '.apple-sequence',
scrollWith: '.apple-container',
images: appleSequenceImages,
imagesRoot: 'https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/02-head-bob-turn/',
priorityFrames: [0],
cover: true,
playUntil: 'scroll-out',
starts: 'in'
});

// END SCROLL_SEQUENCE CODE

});

你有收到任何控制台信息或错误吗? - General Grievance
警告:你的 CodePen 占用了很多内存。 - Posandu
它对我来说工作得很好,你使用的Safari版本是什么? - Naveen Setty
我刚刚测试了一下,它在Safari版本:Versjon 15.0(16612.1.29.41.4, 16612)上可以工作。早于7的Safari版本不支持requestAnimationFrame和其他动画效果,所以最好检查一下Safari版本。 - Donald Shahini
1个回答

0

我刚在Safari上测试了你的代码,对我来说它运行良好。我甚至在控制台中检查了一下,没有任何错误,只是加载的图像名称。

尝试清除缓存并再次检查。如果您得到相同的结果,请尝试使用Safari预览

使用的Safari版本:14.1.2


2
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - sorosh_sabz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接