preview.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import React, { ReactNode } from "react";
  2. import { PreviewContext } from "./previewContext";
  3. import BaseComponent from "../_base/baseComponent";
  4. import PropTypes, { array } from "prop-types";
  5. import { PreviewProps, PreviewState } from "./interface";
  6. import PreviewInner from "./previewInner";
  7. import PreviewFoundation from "@douyinfe/semi-foundation/image/previewFoundation";
  8. import { getUuidShort } from "@douyinfe/semi-foundation/utils/uuid";
  9. import { cssClasses } from "@douyinfe/semi-foundation/image/constants";
  10. import { isObject } from "lodash";
  11. import "@douyinfe/semi-foundation/image/image.scss";
  12. const prefixCls = cssClasses.PREFIX;
  13. export default class Preview extends BaseComponent<PreviewProps, PreviewState> {
  14. static propTypes = {
  15. style: PropTypes.object,
  16. className: PropTypes.string,
  17. visible: PropTypes.bool,
  18. src: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  19. currentIndex: PropTypes.number,
  20. defaultIndex: PropTypes.number,
  21. defaultVisible: PropTypes.bool,
  22. maskClosable: PropTypes.bool,
  23. closable: PropTypes.bool,
  24. zoomStep: PropTypes.number,
  25. infinite: PropTypes.bool,
  26. showTooltip: PropTypes.bool,
  27. closeOnEsc: PropTypes.bool,
  28. prevTip: PropTypes.string,
  29. nextTip: PropTypes.string,
  30. zoomInTip: PropTypes.string,
  31. zoomOutTip: PropTypes.string,
  32. downloadTip: PropTypes.string,
  33. adaptiveTip: PropTypes.string,
  34. originTip: PropTypes.string,
  35. lazyLoad: PropTypes.bool,
  36. lazyLoadMargin: PropTypes.string,
  37. preLoad: PropTypes.bool,
  38. preLoadGap: PropTypes.number,
  39. disableDownload: PropTypes.bool,
  40. zIndex: PropTypes.number,
  41. renderHeader: PropTypes.func,
  42. renderPreviewMenu: PropTypes.func,
  43. getPopupContainer: PropTypes.func,
  44. onVisibleChange: PropTypes.func,
  45. onChange: PropTypes.func,
  46. onClose: PropTypes.func,
  47. onZoomIn: PropTypes.func,
  48. onZoomOut: PropTypes.func,
  49. onPrev: PropTypes.func,
  50. onNext: PropTypes.func,
  51. onDownload: PropTypes.func,
  52. onRotateLeft: PropTypes.func,
  53. onRatioChange: PropTypes.func,
  54. }
  55. static defaultProps = {
  56. src: [],
  57. lazyLoad: true,
  58. lazyLoadMargin: "0px 100px 100px 0px",
  59. };
  60. get adapter() {
  61. return {
  62. ...super.adapter,
  63. };
  64. }
  65. foundation: PreviewFoundation;
  66. previewGroupId: string;
  67. previewRef: React.RefObject<PreviewInner>;
  68. constructor(props) {
  69. super(props);
  70. this.state = {
  71. currentIndex: props.currentIndex || props.defaultCurrentIndex || 0,
  72. visible: props.visible || props.currentDefaultVisible || false,
  73. };
  74. this.foundation = new PreviewFoundation(this.adapter);
  75. this.previewGroupId = getUuidShort({ prefix: "semi-image-preview-group", length: 4 });
  76. this.previewRef = React.createRef<PreviewInner>();
  77. }
  78. componentDidMount() {
  79. const { lazyLoadMargin } = this.props;
  80. const allElement = document.querySelectorAll(`.${prefixCls}-img`);
  81. // use IntersectionObserver to lazy load image
  82. const observer = new IntersectionObserver(entries => {
  83. entries.forEach(item => {
  84. const src = (item.target as any).dataset?.src;
  85. if (item.isIntersecting && src) {
  86. (item.target as any).src = src;
  87. observer.unobserve(item.target);
  88. }
  89. });
  90. },
  91. {
  92. root: document.querySelector(`#${this.previewGroupId}`),
  93. rootMargin: lazyLoadMargin,
  94. }
  95. );
  96. allElement.forEach(item => observer.observe(item));
  97. }
  98. static getDerivedStateFromProps(props: PreviewProps, state: PreviewState) {
  99. const willUpdateStates: Partial<PreviewState> = {};
  100. if (("currentIndex" in props) && (props.currentIndex !== state.currentIndex)) {
  101. willUpdateStates.currentIndex = props.currentIndex;
  102. }
  103. if (("visible" in props) && (props.visible !== state.visible)) {
  104. willUpdateStates.visible = props.visible;
  105. }
  106. return willUpdateStates;
  107. }
  108. handleVisibleChange = (newVisible : boolean) => {
  109. this.foundation.handleVisibleChange(newVisible);
  110. };
  111. handleCurrentIndexChange = (index: number) => {
  112. this.foundation.handleCurrentIndexChange(index);
  113. };
  114. loopImageIndex = () => {
  115. const { children } = this.props;
  116. let index = 0;
  117. const srcListInChildren = [];
  118. const titles: ReactNode [] = [];
  119. const loop = (children) => {
  120. return React.Children.map(children, (child) => {
  121. if (child && child.props && child.type) {
  122. if (child.type.isSemiImage) {
  123. const { src, preview, alt } = child.props;
  124. if (preview) {
  125. const previewSrc = isObject(preview) ? ((preview as any).src ?? src) : src;
  126. srcListInChildren.push(previewSrc);
  127. titles.push(preview?.previewTitle);
  128. return React.cloneElement(child, { imageID: index++ });
  129. }
  130. return child;
  131. }
  132. }
  133. if (child && child.props && child.props.children) {
  134. return React.cloneElement(child, {
  135. children: loop(child.props.children),
  136. });
  137. }
  138. return child;
  139. });
  140. };
  141. return {
  142. srcListInChildren,
  143. newChildren: loop(children),
  144. titles,
  145. };
  146. };
  147. render() {
  148. const { src, style, lazyLoad, ...restProps } = this.props;
  149. const { currentIndex, visible } = this.state;
  150. const { srcListInChildren, newChildren, titles } = this.loopImageIndex();
  151. const srcArr = Array.isArray(src) ? src : (typeof src === "string" ? [src] : []);
  152. const finalSrcList = [...srcArr, ...srcListInChildren];
  153. return (
  154. <PreviewContext.Provider
  155. value={{
  156. isGroup: finalSrcList.length > 1,
  157. previewSrc: finalSrcList,
  158. titles: titles,
  159. currentIndex,
  160. visible,
  161. lazyLoad,
  162. setCurrentIndex: this.handleCurrentIndexChange,
  163. handleVisibleChange: this.handleVisibleChange,
  164. }}
  165. >
  166. <div id={this.previewGroupId} style={style} className={`${prefixCls}-preview-group`}>
  167. {newChildren}
  168. </div>
  169. <PreviewInner
  170. {...restProps}
  171. ref={this.previewRef}
  172. src={finalSrcList}
  173. currentIndex={currentIndex}
  174. visible={visible}
  175. onVisibleChange={this.handleVisibleChange}
  176. />
  177. </PreviewContext.Provider>
  178. );
  179. }
  180. }