Viewer.jsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { Component, createRef } from "react";
  2. import PropTypes from "prop-types";
  3. import { gsap, ScrollTrigger } from "gsap/all";
  4. import styled from "@emotion/styled";
  5. import { css } from "@emotion/react";
  6. export default class Viewer extends Component {
  7. constructor() {
  8. super();
  9. console.log("hello constructor");
  10. //ref
  11. this.containerRef = createRef(null);
  12. this.viewerRef = createRef(null);
  13. this.canvasContainerRef = createRef(null);
  14. this.loadingWrap = createRef(null);
  15. this.viewerOffsetRef = createRef(null);
  16. this.canvasRef = createRef();
  17. this.sequence = [];
  18. this.loadedRenderPool = [];
  19. this.enterTimeline = false;
  20. this.exitTimeline = false;
  21. this.loadComplete = false;
  22. this.loadedCount = 0;
  23. this.progress = 0;
  24. this.lastFrame = -1;
  25. this.floatFrame = 0;
  26. this.loadedRenderTimeout = null;
  27. this.poolAnimateDelay = 40;
  28. this.context = null;
  29. this.width = 1552;
  30. this.height = 873;
  31. this.justScrolled = false;
  32. this.isBelow = true;
  33. this.isAbove = false;
  34. }
  35. static propTypes = {
  36. name: PropTypes.string,
  37. height: PropTypes.string,
  38. path: PropTypes.string,
  39. frameCount: PropTypes.number,
  40. enterTween: PropTypes.func,
  41. exitTween: PropTypes.func,
  42. canvasSize: PropTypes.array,
  43. pause: PropTypes.object,
  44. children: PropTypes.object,
  45. };
  46. state = {};
  47. componentWillUnmount() {
  48. this.timeline && this.timeline.kill(true);
  49. }
  50. componentDidMount() {
  51. this.loadAssets();
  52. this.canvasRef.current && this.initializeCanvas();
  53. if (!this.timeline) {
  54. this.initializeTimeline();
  55. this.setTimeline();
  56. }
  57. if (!this.enterTimeline && this.props.enterTween) {
  58. this.initializeEnterTween();
  59. }
  60. ScrollTrigger.refresh();
  61. }
  62. loadImage(index) {
  63. const img = new Image();
  64. // console.log("index", this.getSourcePath(index));
  65. img.retried = 0;
  66. img.src = this.getSourcePath(index);
  67. img.ogSrc = img.src;
  68. if (this.props.pause && index + "" in this.props.pause) {
  69. for (var r = this.props.pause[index]; r--; ) {
  70. this.sequence.push(img);
  71. }
  72. }
  73. this.sequence.push(img);
  74. img.onload = () => {
  75. index === 1 && this.renderImageToCanvas(0);
  76. if (this.frame > index && this.timeline.scrollTrigger.isActive) {
  77. this.poolNewFrames(index - 1);
  78. }
  79. this.loadedCount += 1;
  80. if (this.loadedCount === parseFloat(this.props.frameCount) - 1) {
  81. this.loadingComplete();
  82. }
  83. };
  84. }
  85. getSourcePath(index) {
  86. const defaultPrefix = import.meta.env.VITE_APP_SOURCE;
  87. return `${defaultPrefix}${this.props.path}/${"".concat(
  88. index.toString().padStart(4, "0")
  89. )}.webp`;
  90. }
  91. loadAssets() {
  92. this.loadImage(1);
  93. setTimeout(() => {
  94. for (var t = 2; t <= this.props.frameCount; t += 1) {
  95. this.loadImage(t);
  96. }
  97. }, 60);
  98. }
  99. loadingComplete() {
  100. console.log(this.props.path, "loading complete");
  101. this.loadComplete = true;
  102. this.isAbove && this.renderImageToCanvas(this.loadedCount - 1);
  103. }
  104. initializeCanvas() {
  105. this.context = this.canvasRef.current.getContext("2d", {
  106. alpha: false,
  107. desynchronized: true,
  108. powerPreference: "high-performance",
  109. });
  110. this.context.imageSmoothingEnabled = true;
  111. this.context.imageSmoothingQuality = "high";
  112. }
  113. initializeTimeline() {
  114. const openLoading = () => {
  115. gsap.to(this.loadingWrap.current, {
  116. autoAlpha: 1,
  117. });
  118. };
  119. const closeLoading = () => {
  120. gsap.to(this.loadingWrap.current, {
  121. autoAlpha: 0,
  122. });
  123. };
  124. closeLoading();
  125. this.timeline = gsap.timeline({
  126. scrollTrigger: {
  127. trigger: this.containerRef.current,
  128. pin: this.viewerRef.current,
  129. scrub: 0.66,
  130. start: "top top",
  131. end: "bottom bottom",
  132. ease: "none",
  133. markers: true,
  134. onUpdate: function (n) {
  135. //处理processloading
  136. },
  137. onScrubComplete: () => {
  138. this.justScrolled = true;
  139. },
  140. onEnter: () => {
  141. closeLoading();
  142. this.isAbove = false;
  143. console.log("onEnter");
  144. },
  145. onEnterBack: () => {
  146. openLoading();
  147. console.log("onEnterBack");
  148. this.isBelow = false;
  149. },
  150. onLeave: () => {
  151. console.log("onLeave");
  152. this.isAbove = true;
  153. openLoading();
  154. },
  155. onLeaveBack: () => {
  156. console.log("onLeaveBack");
  157. openLoading();
  158. this.isBelow = true;
  159. },
  160. },
  161. });
  162. }
  163. setTimeline() {
  164. console.log("this.fullFrameCount", this.fullFrameCount);
  165. this.timeline.to(this, {
  166. floatFrame: this.fullFrameCount - 1,
  167. ease: "none",
  168. onUpdate: () => {
  169. this.frame = Math.floor(this.floatFrame);
  170. if (this.lastFrame === this.frame || this.loadedRenderPool.length > 0) {
  171. this.renderImageToCanvas(this.frame);
  172. }
  173. },
  174. });
  175. }
  176. initializeEnterTween() {
  177. console.log("initializeEnterTween");
  178. }
  179. poolNewFrames(index) {
  180. console.log("poolNewFrames", index);
  181. this.loadedRenderPool.unshift(index);
  182. this.loadedRenderPool.sort(function (e, t) {
  183. return t - e;
  184. });
  185. this.animatePool();
  186. }
  187. animatePool() {
  188. if (!this.loadedRenderTimeout && this.loadedRenderPool.length) {
  189. this.loadedRenderTimeout = setTimeout(() => {
  190. this.loadedRenderTimeout = true;
  191. var poolFrame = this.loadedRenderPool[this.loadedRenderPool.length - 1];
  192. if (poolFrame <= this.frame) {
  193. var remainFrame = this.loadedRenderPool.pop();
  194. this.renderImageToCanvas(remainFrame);
  195. this.animatePool();
  196. }
  197. if (this.frame < poolFrame) {
  198. this.loadedRenderPool = [];
  199. }
  200. }, this.poolAnimateDelay);
  201. }
  202. }
  203. renderImageToCanvas(index) {
  204. if (this.sequence[index]) {
  205. if (this.context.drawImage) {
  206. console.log("renderImageToCanvas", index);
  207. this.context.drawImage(this.sequence[index], 0, 0);
  208. this.lastFrame = index;
  209. } else {
  210. this.initializeCanvas();
  211. }
  212. }
  213. }
  214. render() {
  215. // process props
  216. this.fullFrameCount = this.props.frameCount;
  217. if (this.props.pause) {
  218. Object.keys(this.props.pause).forEach((index) => {
  219. this.fullFrameCount += this.props.pause[index];
  220. });
  221. }
  222. const Wrapper = styled.div({
  223. position: "relative",
  224. margin: "auto",
  225. textAlign: "center",
  226. pointerEvents: "none",
  227. maxWidth: "100vw",
  228. });
  229. return (
  230. <>
  231. <Wrapper
  232. ref={this.containerRef}
  233. style={{ height: this.props.height || "500vh" }}
  234. >
  235. <div>{this.props.name}</div>
  236. <div className="loading-wrap" ref={this.loadingWrap}></div>
  237. <div ref={this.viewerRef}>
  238. <div style={{ overflow: "hidden" }}>
  239. <canvas
  240. css={css`
  241. width: auto;
  242. margin-left: 50%;
  243. transform: translateX(-50%);
  244. height: calc(var(--vh, 1vh) * 100);
  245. `}
  246. ref={this.canvasRef}
  247. width={this.width}
  248. height={this.height}
  249. ></canvas>
  250. </div>
  251. </div>
  252. <>{this.props.children}</>
  253. </Wrapper>
  254. </>
  255. );
  256. }
  257. }