import { Component, createRef } from "react"; import PropTypes from "prop-types"; import { gsap, ScrollTrigger } from "gsap/all"; import LazyLoad from "react-lazyload"; import { css } from "@emotion/react"; const isDebug = Number(import.meta.env.VITE_APP_DEBUG) === 1; console.log("isDebug", isDebug); export default class Viewer extends Component { constructor() { super(); console.log("hello constructor"); //ref this.containerRef = createRef(null); this.viewerRef = createRef(null); this.canvasContainerRef = createRef(null); this.loadingWrap = createRef(null); this.viewerOffsetRef = createRef(null); this.canvasRef = createRef(null); this.processingRef = createRef(null); this.preProcessingRef = createRef(null); this.processBarRef = createRef(null); this.sequence = []; this.loadedRenderPool = []; this.enterTimeline = false; this.exitTimeline = false; this.loadComplete = false; this.playBarTween = false; this.playPreBarTween = false; this.loadedCount = 0; this.progress = 0; this.lastFrame = -1; this.floatFrame = 0; this.frame = 0; this.loadedRenderTimeout = null; this.poolAnimateDelay = 40; this.context = null; this.width = 1552; this.height = 873; this.justScrolled = false; this.lastProgress = false; this.isBelow = true; this.isAbove = false; } static propTypes = { name: PropTypes.string, height: PropTypes.string, path: PropTypes.string, frameCount: PropTypes.number, startFrame: PropTypes.number, enterTween: PropTypes.object, exitTween: PropTypes.object, canvasSize: PropTypes.array, pause: PropTypes.object, children: PropTypes.object, }; componentWillUnmount() { console.error("remove-timeline"); if (this.timeline) { this.timeline.kill(true); } } componentDidMount() { this.fullFrameCount = this.props.frameCount; this.frame = this.props.startFrame || 0; if (this.props.pause) { Object.keys(this.props.pause).forEach((index) => { this.fullFrameCount += this.props.pause[index]; }); } this.loadAssets(); this.canvasRef.current && this.initializeCanvas(); if (!this.timeline) { this.initializeTimeline(); this.setTimeline(); } if (!this.enterTimeline && this.props.enterTween) { this.initializeEnterTween(); } if (!this.exitTimeline && this.props.exitTween) { this.initializeExitTween(); } ScrollTrigger.refresh(); } loadImage(index) { const img = new Image(); img.retried = 0; img.src = this.getSourcePath(index); img.ogSrc = img.src; if (this.props.pause && index + "" in this.props.pause) { for (var r = this.props.pause[index]; r--; ) { this.sequence.push(img); } } this.sequence.push(img); img.onload = () => { index === 1 && this.renderImageToCanvas(0); if (this.frame > index && this.timeline.scrollTrigger.isActive) { this.poolNewFrames(index - 1); } // var t = 100 - (this.frame / this.fullFrameCount) * 100 + "%"; // this.loadingProgress.current.style.width this.loadedCount += 1; if (this.loadedCount === parseFloat(this.props.frameCount) - 1) { this.loadingComplete(); } }; } getSourcePath(index) { const defaultPrefix = import.meta.env.VITE_APP_SOURCE; return `${defaultPrefix}${this.props.path}/${"".concat( index.toString().padStart(4, "0") )}.webp`; } loadAssets() { this.loadImage(1); setTimeout(() => { for (var t = 2; t <= this.props.frameCount; t += 1) { this.loadImage(t); } }, 60); } loadingComplete() { console.log(this.props.path, "loading complete"); this.loadComplete = true; this.isAbove && this.renderImageToCanvas(this.loadedCount - 1); } initializeCanvas() { this.context = this.canvasRef.current.getContext("2d", { alpha: false, desynchronized: true, powerPreference: "high-performance", }); this.context.imageSmoothingEnabled = true; this.context.imageSmoothingQuality = "high"; } initializeTimeline() { // const openLoading = () => { // gsap.to(this.loadingWrap.current, { // autoAlpha: 1, // }); // }; // const closeLoading = () => { // gsap.to(this.loadingWrap.current, { // autoAlpha: 0, // }); // }; // closeLoading(); this.timeline = gsap.timeline({ scrollTrigger: { trigger: this.containerRef.current, pin: this.viewerRef.current, scrub: 0.66, start: "top top", end: "bottom bottom", ease: "none", markers: isDebug, onUpdate: (trigger) => { //处理processloading if (!this.lastProgress) { this.lastProgress = trigger.progress; // console.log("lastProgress", this.lastProgress); } else { if (this.lastProgress !== this.progress) { this.justScrolled = true; this.lastProgress = trigger.progress; } } }, onScrubComplete: () => { this.justScrolled = true; }, onEnter: () => { this.isAbove = false; console.log(this.props.path, "onEnter"); this.enterShowElements(); }, onEnterBack: () => { // openLoading(); console.log(this.props.path, "onEnterBack"); this.isBelow = false; this.enterShowElements(); }, onLeave: () => { console.log(this.props.path, "onLeave"); this.isAbove = true; this.leaveHideElements(); }, onLeaveBack: () => { console.log(this.props.path, "onLeaveBack"); this.isBelow = true; this.leaveHideElements(); }, }, }); } setTimeline() { this.timeline.to(this, { floatFrame: this.fullFrameCount - 1, ease: "none", onUpdate: () => { this.frame = Math.floor(this.floatFrame); if ( this.lastFrame === this.frame || this.loadedRenderPool.length === 0 ) { this.renderImageToCanvas(this.frame); } }, }); } initializeEnterTween() { const duration = this.props.enterTween.duration || 1; let openPin = false; console.warn("this.props.enterTween", duration, this.props.enterTween); gsap.set(this.viewerRef.current, { yPercent: -100 * duration, }); if (void 0 !== this.props.enterTween.pin) { openPin = this.props.enterTween.pin; } this.enterTimeline = gsap.timeline({ scrollTrigger: { trigger: this.viewerRef.current, scrub: true, pin: openPin, start: function () { return "top top"; }, end: function () { return "top top-=" + window.innerHeight * duration; }, }, }); if (this.props.enterTween.to) { this.enterTimeline.to( this.viewerRef.current, Object.assign( { ease: "none", }, this.props.enterTween.to ) ); } if (this.props.enterTween.from) { this.enterTimeline.from( this.viewerRef.current, Object.assign( { ease: "none", }, this.props.enterTween.from ) ); } } initializeExitTween() { console.log(this.props.path, "initializing exit tween "); this.exitTimeline = gsap.timeline({ scrollTrigger: { scrub: true, trigger: this.containerRef.current, pin: this.viewerRef.current, onLeave: this.props.exitTween.onLeave, onLeaveBack: this.props.exitTween.onLeaveBack, onEnterBack: this.props.exitTween.onEnterBack, start: function () { return "bottom bottom"; }, end: function () { return "bottom top"; }, }, }); if (this.props.exitTween.from) { this.exitTimeline.from( this.viewerRef.current, Object.assign( { ease: "none", }, this.props.exitTween.from ) ); } if (this.props.exitTween.to) { this.exitTimeline.to( this.viewerRef.current, Object.assign( { ease: "none", }, this.props.exitTween.to ) ); } } poolNewFrames(index) { this.loadedRenderPool.unshift(index); this.loadedRenderPool.sort(function (a, b) { return b - a; }); this.animatePool(); } animatePool() { if (!this.loadedRenderTimeout && this.loadedRenderPool.length) { this.loadedRenderTimeout = setTimeout(() => { this.loadedRenderTimeout = false; var poolFrame = this.loadedRenderPool[this.loadedRenderPool.length - 1]; if (poolFrame <= this.frame) { var remainFrame = this.loadedRenderPool.pop(); this.renderImageToCanvas(remainFrame); this.animatePool(); } if (this.frame < poolFrame) { this.loadedRenderPool = []; } }, this.poolAnimateDelay); } } renderImageToCanvas(index) { if (this.sequence[index]) { if (this.context.drawImage) { this.context.drawImage(this.sequence[index], 0, 0); this.lastFrame = index; this.handleSyncProessBar(index); } else { this.initializeCanvas(); } } } handleSyncProessBar(index) { const progressingPreload = 100 - (this.frame / this.fullFrameCount) * 100 + "%"; const progressing = 100 - (index / this.fullFrameCount) * 100 + "%"; // console.log("handleSyncProessBar", this.processingRef.current); this.playPreBarTween = gsap.to(this.preProcessingRef.current, { duration: 0.05, right: progressingPreload, ease: "none", }); this.playBarTween = gsap.to(this.processingRef.current, { duration: 0.05, right: progressing, ease: "none", }); } enterShowElements() { gsap.set(this.processBarRef.current, { autoAlpha: 1, }); } leaveHideElements() { gsap.set(this.processBarRef.current, { autoAlpha: 0, }); } render() { return ( <>