Ant*_*nta 6 html scroll canvas
我正在使用下面 Codepen 中的代码来创建一个全角画布,用于模拟滚动图像序列上的视频播放。
\n它在 Chrome 中工作正常,但我无法使其在 Safari \xe2\x80\x93 上工作,它只是显示为白色,就好像它是空的一样。如果我检查该元素,它似乎不存在于 Safari 中。
\n是否存在已知问题或可以在代码中修复的问题?
\n代码笔: https: //codepen.io/SalmanShaikh/pen/MWeNBLL
\nconst appleSequenceImages = [];\n//number of images 131; fill the array\n//see the squence obj below for image path.\nfor (let i = 0; i <= 374; i++) {\n console.log(`${`${i}`.slice(-4)}.jpg`);\n appleSequenceImages.push(`${`${i}`.slice(-4)}.jpg`);\n}\n\n\nconst requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;\n\nclass EventEmitter {\n listeners = {}\n addListener(eventName, fn) {\n this.listeners[eventName] = this.listeners[eventName] || [];\n this.listeners[eventName].push(fn);\n return this;\n }\n on(eventName, fn) {\n return this.addListener(eventName, fn);\n }\n once(eventName, fn) {\n this.listeners[eventName] = this.listeners[eventName] || [];\n const onceWrapper = () => {\n fn();\n this.off(eventName, onceWrapper);\n }\n this.listeners[eventName].push(onceWrapper);\n return this;\n }\n off(eventName, fn) {\n return this.removeListener(eventName, fn);\n }\n removeListener(eventName, fn) {\n let lis = this.listeners[eventName];\n if (!lis) return this;\n for (let i = lis.length; i > 0; i--) {\n if (lis[i] === fn) {\n lis.splice(i, 1);\n break;\n }\n }\n return this;\n }\n emit(eventName, ...args) {\n let fns = this.listeners[eventName];\n if (!fns) return false;\n fns.forEach((f) => {\n f(...args);\n });\n return true;\n }\n listenerCount(eventName) {\n let fns = this.listeners[eventName] || [];\n return fns.length;\n }\n rawListeners(eventName) {\n return this.listeners[eventName];\n }\n}\nclass Canvas {\n constructor(e) {\n this.images = e.images;\n this.container = e.container;\n this.cover = e.cover;\n this.displayIndex = 0;\n }\n\n setup() {\n this.canvas = document.createElement("canvas");\n this.container.appendChild(this.canvas);\n this.ctx = this.canvas.getContext(\'2d\')\n\n window.addEventListener(\'resize\', () => this.resize());\n this.resize();\n }\n\n renderIndex(e) {\n if (this.images[e]) {\n return this.drawImage(e);\n }\n // Find closest loaded image\n for (var t = Number.MAX_SAFE_INTEGER, r = e; r >= 0; r--)\n if (this.images[r]) {\n t = r;\n break\n }\n for (var n = Number.MAX_SAFE_INTEGER, i = e, o = this.images.length; i < o; i++)\n if (this.images[i]) {\n n = i;\n break\n }\n this.images[t] ? this.drawImage(t) : this.images[n] && this.drawImage(n)\n }\n\n drawImage(e) {\n this.displayIndex = e,\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n const x = Math.floor((this.canvas.width - this.images[this.displayIndex].naturalWidth) / 2);\n const y = Math.floor((this.canvas.height - this.images[this.displayIndex].naturalHeight) / 2);\n if (this.cover) {\n\n this.drawImageCover(this.ctx, this.images[this.displayIndex]);\n } else {\n this.ctx.drawImage(this.images[this.displayIndex], x, y);\n }\n }\n\n resize() {\n const w = this.container.clientWidth;\n const h = this.container.clientHeight;\n this.canvas.style.height = `${h}px`;\n this.canvas.style.width = `${w}px`;\n this.canvas.height = h;\n this.canvas.width = w;\n\n this.renderIndex(this.displayIndex);\n }\n\n /**\n * Main code by Ken Fyrstenberg Nilsen, modified by me.\n *\n * drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])\n *\n * If image and context are only arguments rectangle will equal canvas\n */\n drawImageCover(ctx, img, x, y, w, h, offsetX, offsetY) {\n\n if (arguments.length === 2) {\n x = y = 0;\n w = ctx.canvas.width;\n h = ctx.canvas.height;\n }\n\n // default offset is center\n offsetX = typeof offsetX === "number" ? offsetX : 0.5;\n offsetY = typeof offsetY === "number" ? offsetY : 0.5;\n\n // keep bounds [0.0, 1.0]\n if (offsetX < 0) offsetX = 0;\n if (offsetY < 0) offsetY = 0;\n if (offsetX > 1) offsetX = 1;\n if (offsetY > 1) offsetY = 1;\n //added new prop.width and height for precise movement\n var iw = img.width,\n ih = img.height,\n r = Math.min(w / iw, h / ih),\n nw = iw * r, // new prop. width\n nh = ih * r, // new prop. height\n cx, cy, cw, ch, ar = 1;\n\n // decide which gap to fill\n if (nw < w) ar = w / nw;\n if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated\n nw *= ar;\n nh *= ar;\n\n // calc source rectangle\n cw = iw / (nw / w);\n ch = ih / (nh / h);\n\n cx = (iw - cw) * offsetX;\n cy = (ih - ch) * offsetY;\n\n // make sure source rectangle is valid\n if (cx < 0) cx = 0;\n if (cy < 0) cy = 0;\n if (cw > iw) cw = iw;\n if (ch > ih) ch = ih;\n\n // fill image in dest. rectangle\n ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);\n }\n}\nclass ImgLoader extends EventEmitter {\n constructor(opts) {\n super();\n this.images = opts.imgsRef;\n this.imageNames = opts.images;\n this.imagesRoot = opts.imagesRoot;\n this.sequenceLength = opts.images.length;\n this.priorityFranes = opts.priorityFrames;\n this.complete = false;\n this.loadIndex = 0;\n\n this.priorityQueue = this.createPriorityQueue();\n this.loadingQueue = this.createLoadingQueue();\n\n this.loadNextImage();\n }\n\n loadImage(e) {\n if (this.images[e]) {\n return this.loadNextImage();\n }\n const onLoad = () => {\n img.removeEventListener(\'load\', onLoad);\n this.images[e] = img;\n\n if (e === 0) {\n this.emit(\'FIRST_IMAGE_LOADED\');\n }\n this.loadNextImage();\n }\n const img = new Image;\n img.addEventListener(\'load\', onLoad);\n img.src = (this.imagesRoot ? this.imagesRoot : \'\') + this.imageNames[e];\n }\n\n loadNextImage() {\n if (this.priorityQueue.length) {\n this.loadImage(this.priorityQueue.shift());\n if (!this.priorityQueue.length) {\n this.emit(\'PRIORITY_IMAGES_LOADED\');\n }\n } else if (this.loadingQueue.length) {\n this.loadImage(this.loadingQueue.shift())\n } else {\n this.complete = true;\n this.emit(\'IMAGES_LOADED\');\n }\n }\n\n createPriorityQueue() {\n const p = this.priorityFrames || [];\n if (!p.length) {\n p.push(0);\n p.push(Math.round(this.sequenceLength / 2));\n p.push(this.sequenceLength - 1);\n }\n return p;\n }\n\n createLoadingQueue() {\n return this.imageNames.map((s, i) => i).sort((e, n) => {\n return Math.abs(e - this.sequenceLength / 2) - Math.abs(n - this.sequenceLength / 2)\n });\n }\n}\nclass ScrollSequence {\n constructor(opts) {\n this.opts = {\n container: \'body\',\n starts: \'out\',\n ends: \'out\',\n imagesRoot: \'\',\n cover: false,\n ...opts\n }\n this.container = typeof opts.container === \'object\' ?\n opts.container :\n document.querySelector(opts.container);\n\n this.scrollWith = !opts.scrollWith ?\n this.container :\n typeof opts.scrollWith === \'object\' ?\n opts.scrollWith :\n document.querySelector(opts.scrollWith);\n\n this.images = Array(opts.images.length);\n this.imagesToLoad = opts.images;\n this.priorityFrames = opts.priorityFrames;\n\n this.loader = new ImgLoader({\n imgsRef: this.images,\n images: this.imagesToLoad,\n imagesRoot: this.opts.imagesRoot,\n priorityFrames: this.priorityFrames\n });\n\n this.canvas = new Canvas({\n container: this.container,\n images: this.images,\n cover: this.opts.cover\n });\n\n this.init();\n }\n\n init() {\n this.canvas.setup();\n this.loader.once(\'FIRST_IMAGE_LOADED\', () => {\n this.canvas.renderIndex(0);\n })\n this.loader.once(\'PRIORITY_IMAGES_LOADED\', () => {\n window.addEventListener(\'scroll\', () => this.changeOnWindowScroll());\n })\n this.loader.once(\'IMAGES_LOADED\', () => {\n console.log(\'Sequence Loaded\');\n })\n }\n\n changeOnWindowScroll() {\n const step = 100 / (this.images.length - 1);\n const mapToIndex = Math.floor(this.percentScrolled / step);\n requestAnimationFrame(() => this.canvas.renderIndex(mapToIndex));\n }\n\n get percentScrolled() {\n const {\n starts,\n ends\n } = this.opts;\n const el = this.scrollWith;\n const doc = document.documentElement;\n const clientOffsety = doc.scrollTop || window.pageYOffset;\n const elementHeight = el.clientHeight || el.offsetHeight;\n const clientHeight = doc.clientHeight;\n let target = el;\n let offsetY = 0;\n do {\n offsetY += target.offsetTop;\n target = target.offsetParent;\n } while (target && target !== window);\n\n let u = (clientOffsety - offsetY);\n let d = (elementHeight + clientHeight)\n\n if (starts === \'out\') u += clientHeight;\n if (ends === \'in\') d -= clientHeight;\n if (starts == \'in\') d -= clientHeight;\n\n const value = u / d * 100;\n return value > 100 ? 100 : value < 0 ? 0 : value;\n }\n}\n\nconst appleSequence = new ScrollSequence({\ncontainer: \'.apple-sequence\',\nscrollWith: \'.apple-container\',\nimages: appleSequenceImages,\nimagesRoot: \'https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/02-head-bob-turn/\',\npriorityFrames: [0],\ncover: true,\nplayUntil: \'scroll-out\',\nstarts: \'in\'\n});\n\n// END SCROLL_SEQUENCE CODE\n\n});\nRun Code Online (Sandbox Code Playgroud)\n
小智 0
我刚刚在 Safari 上测试了你的代码,它对我来说工作得很好。我什至在控制台中检查过,没有任何错误,只是加载了图像名称。
尝试清理缓存并再次检查。如果您得到相同的结果,请尝试使用 Safari 预览
使用的 Safari 版本:14.1.2