import { Component, createRef, Children, isValidElement, cloneElement, } from "react"; import PropTypes from "prop-types"; import { gsap, ScrollTrigger } from "gsap/all"; import LazyLoad from "react-lazyload"; import { css } from "@emotion/react"; // import { Style } from "../style/viewer"; export default function Viewer(props) { const lazyHeight = 0.01 * parseFloat(props.height) * window.innerHeight; // offset={1e4} var allChildren = Children.map(props.children, function (element) { return isValidElement(element) ? cloneElement(element, { parentHeight: props.height, debug: props.debug, }) : element; }); const debug = props.debug || false; // console.log("lazyHeight", lazyHeight); return ( {allChildren} ); } Viewer.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.any, debug: PropTypes.bool, scrollSpeed: PropTypes.number, }; class ViewerInner extends Component { constructor(props) { super(); //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; props.debug && console.log("init ", props.name); } static propTypes = Viewer.propTypes; componentWillUnmount() { this.props.debug && console.warn("remove-timeline"); if (this.timeline) { this.timeline.kill(true); } } componentDidUpdate() { if (this.props.debug) { this.timeline.scrollTrigger.refresh(); } } 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.onerror = () => { var timeStamp = Math.floor(Date.now() * Math.random()) .toString() .substring(0, 8); img.retried < 2 ? setTimeout(() => { img.src = img.ogSrc + "?" + timeStamp; }, 80) : img.retried < 3 && setTimeout(() => { img.src = img.ogSrc.slice(0, -4) + ".jpg?" + timeStamp; }, 80), img.retried++; this.props.debug && console.log("img.retried", img.retried); }; img.onload = () => { index === 1 && this.renderImageToCanvas(0); if ( this.frame > index && this.timeline && this.timeline.scrollTrigger.isActive ) { this.poolNewFrames(index - 1); } var t = 100 - (this.frame / this.fullFrameCount) * 100 + "%"; if (this.preProcessingRef.current) { this.preProcessingRef.current.style.width = t; } 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: this.props.debug || false, onUpdate: (trigger) => { //处理processloading if (!this.lastProgress) { this.lastProgress = trigger.progress; } 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); if (this.preProcessingRef.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 ( <>
<>{this.props.children}
); } }