preview.tsx 7.0 KB

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