import { isMobile } from "./isMoblie"; import { throttle, debounce, clamp } from "./fn"; import gsap from "gsap"; import mitt from "mitt"; class Mitt { constructor(e) { Object.assign(this, mitt(e)); } } export class CanvasPlayer extends Mitt { constructor(canvasId, setting) { super(); this.canvas = null; this.canvasId = canvasId; this.context = null; this.vw = null; this.vh = null; this.imageW = null; this.imageH = null; this.setting = setting; this.currentFrame = 0; // 当前真正 this.movingFrame = 0; // 当前前进 this.scrollFrame = 0; this.lastScroll = 0; this.isRendering = false; this.localRender = []; this.currentType = 0; this.frames = []; this.clips = []; this.canScroll = false; this.resize = this.resize.bind(this); this.watchScroll = this.watchScroll.bind(this); } init() { this.canvas = document.getElementById(this.canvasId); this.context = this.canvas.getContext("2d", { alpha: true, desynchronized: true, powerPreference: "high-performance", }); this.vw = this.canvas.width = window.innerWidth; this.vh = this.canvas.height = window.innerHeight; this.imageW = isMobile() ? 750 : 1920; this.imageH = isMobile() ? 1334 : 1080; this.proload(); this.initClipAnimate(); this.loadEvent(); } loadImage(url) { return new Promise((resolve, reject) => { const img = new Image(this.imageW, this.imageH); img.onload = () => { resolve(img); }; img.onerror = (error) => { resolve(); }; img.src = url; }); } proload() { if (this.setting) { const list = []; const total = Array.from(this.setting).reduce( (pre, current) => pre + current["total"], 0 ); Array.from(this.setting).forEach((item, framekey) => { const base = []; const clip = { id: item.sectionType, total: item.total, x: 0, animation: null, }; this.clips.push(clip); for (let key = 0; key < item.total; key++) { const padLength = item.total.toString().length + 1; let imgIndex = String(key).padStart(padLength, "0"); let url = `${item.imageUrl}/${imgIndex}.webp`; const res = this.loadImage(url).then((image) => { if (image) { const frame = { id: item.sectionType, index: key, image: image, total: item.total, }; this.context.drawImage(image, 0, 0, this.vw, this.vh); this.frames.push(frame); const cu = key + framekey * item.total; const process = Math.floor(Number(cu / total) * 100); this.emit("updatePress", process); } }); list.push(res); } }); Promise.all(list).then(() => { console.warn("all load"); this.emit("updatePress", 100); this.emit("loaded"); }); } } loadEvent() { window.addEventListener("resize", this.resize, false); window.addEventListener("wheel", this.watchScroll, false); } unLoadEvent() { window.removeEventListener("resize", this.resize, false); window.removeEventListener("wheel", this.watchScroll, false); } resize() { this.vw = this.canvas.width = window.innerWidth; this.vh = this.canvas.height = window.innerHeight; } enableScroll(type = 0) { this.canScroll = true; this.currentType = type; this.initFirstFrame(); } unEnableScroll() { this.canScroll = false; } manualScroll(event, type) { if (!this.canScroll) { this.enableScroll(type); this.initFirstFrame(); } const scrollY = event.target.scrollTop; if (scrollY > 0 && this.lastScroll <= scrollY) { this.lastScroll = scrollY; // console.log("Scrolling DOWN"); this.toScroll(scrollY, true, event); } else { this.lastScroll = scrollY; // console.log("Scrolling UP"); this.toScroll(scrollY, false, event); } // deltaY = scrollY - lastKnownScrollPosition; // const deltaHeight = clip.total * 100 - window.innerHeight; // const prcess = scrollY / deltaHeight; // const frame = Math.round(clip.total * prcess); } toScroll(scrollY, na, event) { let timer, completeTimer; const clip = Array.from(this.clips).find( (item) => item.id === this.currentType ); // const deltaHeight = clip.total * 100 + window.innerHeight; // const prcess = scrollY / deltaHeight; // const startFrame = Math.floor(clip.total * prcess); const startFrame = this.getframeByHeight(scrollY); if (timer) { clearTimeout(timer); timer = null; } this.currentFrame = startFrame; const matchDis = 10; const dynamicDistance = na ? this.currentFrame + matchDis : this.currentFrame - matchDis; // 少于frame const dis = clamp(dynamicDistance, 0, clip.total); console.log("startFrame", this.currentFrame, dis, this.movingFrame); if (this.scrollAni) { // console.log("11", this.scrollAni.duration()); this.scrollAni.kill(); this.scrollAni = null; } timer = setTimeout(() => { this.scrollAni = gsap.timeline(); this.isRendering = true; this.scrollAni.to(this, { movingFrame: dis, // ease: "power1.inOut", // duration: 0.6, yoyo: true, onUpdate: () => { this.isRendering = true; const mFrame = Math.floor(this.movingFrame); this.reDraw(mFrame, this.currentType); }, onStart: () => {}, onComplete: () => { completeTimer = setTimeout(this.toRunPatch, 0); this.scrollAni && this.scrollAni.pause(); }, }); }, 40); } watchScroll(event) { if (this.canScroll) { if (this.currentFrame < 0) { this.currentFrame = 0; } if (event.deltaY > 0) { // this.autoIncrement(true); } else { // this.autoIncrement(false); } } } getframeByHeight(height) { if (this.currentType) { const clip = Array.from(this.clips).find( (item) => item.id === this.currentType ); const deltaHeight = clip.total * 100 - window.innerHeight; const prcess = height / deltaHeight; const frame = Math.round(clip.total * prcess); return clamp(frame, 0, clip.total); } return 0; } getFrameScrollTop(frame) { if (this.currentType) { const clip = Array.from(this.clips).find( (item) => item.id === this.currentType ); const updateHeight = clamp(frame * 100, 0, clip.total * 100); return updateHeight; } return 0; } updateScrollTop(frame) { if (this.currentType) { const bar = document.querySelector(`.scroll-bar-${this.currentType}`); const updateHeight = frame * 100 + window.innerHeight; console.log("updateScrollTop", updateHeight); bar.style.scrollTop = updateHeight; } } initClipScrollheight() { if (this.currentType) { const bar = document.querySelector( `.scroll-bar-${this.currentType}-placeholder` ); const clip = Array.from(this.clips).find( (item) => item.id === this.currentType ); const deltaHeight = clip.total * 100; bar.style.height = `${deltaHeight}px`; } } resetClipScrollTop() { if (this.currentType) { const bar = document.querySelector(`.scroll-bar-${this.currentType}`); bar.style.scrollTop = 0; } } noticeProcess() { const clip = Array.from(this.clips).find( (item) => item.id === this.currentType ); const process = Number(this.currentFrame / clip.total) * 100; this.emit("process", { process, type: this.currentType, }); } test() { const height = this.getFrameScrollTop(this.currentFrame); console.log("target-height", height); document.querySelector(".scroll-bar-3").scrollTop = height; } play(frame) { console.log("play", frame); const height = this.getFrameScrollTop(frame); console.log("target-height", height); this.lastScroll = height; this.currentFrame = frame; document.querySelector(".scroll-bar-3").scrollTo({ top: height, left: 0 }); } initClipAnimate() { Array.from(this.clips).forEach((item, key) => { const duration = this.clips[key].total / 15; const anmi = gsap.to(this.clips[key], duration, { x: this.clips[0].total - 1, repeat: 0, duration: (this.clips[key].total / 2) * 1000, ease: "none", yoyo: true, onComplete: () => { console.log("done"); }, onUpdate: () => { const frame = Math.floor(this.clips[key].x); this.reDraw(frame, this.currentType); }, }); anmi.pause(); this.clips[key].animation = anmi; }); } autoPlay() { console.log("this.clips[0]", this.clips[0].animation); this.clips[0].animation.restart(); } initFirstFrame() { if (this.currentType) { const frameItem = this.frames.find( (item) => item.id == this.currentType && item.index == 1 ); this.context.clearRect(0, 0, this.vw, this.vh); this.context.drawImage(frameItem.image, 0, 0, this.vw, this.vh); this.initClipScrollheight(); this.scrollAni = gsap.timeline(); let doneScroll = () => { this.isRendering = false; if (this.currentFrame === 0) { this.emit("scroll-prev"); } if (this.currentFrame === frameItem.total) { this.emit("scroll-next"); } console.warn("scroll done", this.currentFrame, this.movingFrame); }; this.toRunPatch = debounce(doneScroll, 400); console.log("initFirstFrame"); } } reDraw(frame, type) { if (isMobile()) { } else { const frameItem = this.frames.find( (item) => item.id == type && item.index == frame ); if (frameItem && frameItem.index <= frameItem.total) { this.context.clearRect(0, 0, this.vw, this.vh); this.context.drawImage(frameItem.image, 0, 0, this.vw, this.vh); } } } update() {} }