index.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import './index.less';
  2. import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
  3. // @ts-ignore
  4. import { basicProps } from './props';
  5. // @ts-ignore
  6. import { Props } from './types';
  7. import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
  8. // import { Spin } from 'ant-design-vue';
  9. import resumeSvg from '/@/assets/svg/preview/resume.svg';
  10. import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
  11. import scaleSvg from '/@/assets/svg/preview/scale.svg';
  12. import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
  13. import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
  14. enum StatueEnum {
  15. LOADING,
  16. DONE,
  17. FAIL,
  18. }
  19. interface ImgState {
  20. currentUrl: string;
  21. imgScale: number;
  22. imgRotate: number;
  23. imgTop: number;
  24. imgLeft: number;
  25. currentIndex: number;
  26. status: StatueEnum;
  27. moveX: number;
  28. moveY: number;
  29. show: boolean;
  30. }
  31. const prefixCls = 'img-preview';
  32. export default defineComponent({
  33. name: 'ImagePreview',
  34. props: basicProps,
  35. setup(props: Props) {
  36. const imgState = reactive<ImgState>({
  37. currentUrl: '',
  38. imgScale: 1,
  39. imgRotate: 0,
  40. imgTop: 0,
  41. imgLeft: 0,
  42. status: StatueEnum.LOADING,
  43. currentIndex: 0,
  44. moveX: 0,
  45. moveY: 0,
  46. show: props.show,
  47. });
  48. const wrapElRef = ref<HTMLDivElement | null>(null);
  49. const imgElRef = ref<HTMLImageElement | null>(null);
  50. // 初始化
  51. function init() {
  52. initMouseWheel();
  53. const { index, imageList } = props;
  54. if (!imageList || !imageList.length) {
  55. throw new Error('imageList is undefined');
  56. }
  57. imgState.currentIndex = index;
  58. handleIChangeImage(imageList[index]);
  59. }
  60. // 重置
  61. function initState() {
  62. imgState.imgScale = 1;
  63. imgState.imgRotate = 0;
  64. imgState.imgTop = 0;
  65. imgState.imgLeft = 0;
  66. }
  67. // 初始化鼠标滚轮事件
  68. function initMouseWheel() {
  69. const wrapEl = unref(wrapElRef);
  70. if (!wrapEl) {
  71. return;
  72. }
  73. (wrapEl as any).onmousewheel = scrollFunc;
  74. // 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
  75. document.body.addEventListener('DOMMouseScroll', scrollFunc);
  76. // 禁止火狐浏览器下拖拽图片的默认事件
  77. document.ondragstart = function () {
  78. return false;
  79. };
  80. }
  81. // 监听鼠标滚轮
  82. function scrollFunc(e: any) {
  83. e = e || window.event;
  84. e.delta = e.wheelDelta || -e.detail;
  85. e.preventDefault();
  86. if (e.delta > 0) {
  87. // 滑轮向上滚动
  88. scaleFunc(0.015);
  89. }
  90. if (e.delta < 0) {
  91. // 滑轮向下滚动
  92. scaleFunc(-0.015);
  93. }
  94. }
  95. // 缩放函数
  96. function scaleFunc(num: number) {
  97. if (imgState.imgScale <= 0.2 && num < 0) return;
  98. imgState.imgScale += num;
  99. }
  100. // 旋转图片
  101. function rotateFunc(deg: number) {
  102. imgState.imgRotate += deg;
  103. }
  104. // 鼠标事件
  105. function handleMouseUp() {
  106. const imgEl = unref(imgElRef);
  107. if (!imgEl) return;
  108. imgEl.onmousemove = null;
  109. }
  110. // 更换图片
  111. function handleIChangeImage(url: string) {
  112. imgState.status = StatueEnum.LOADING;
  113. const img = new Image();
  114. img.src = url;
  115. img.onload = () => {
  116. imgState.currentUrl = url;
  117. imgState.status = StatueEnum.DONE;
  118. };
  119. img.onerror = () => {
  120. imgState.status = StatueEnum.FAIL;
  121. };
  122. }
  123. // 关闭
  124. function handleClose(e: MouseEvent) {
  125. e && e.stopPropagation();
  126. imgState.show = false;
  127. // 移除火狐浏览器下的鼠标滚动事件
  128. document.body.removeEventListener('DOMMouseScroll', scrollFunc);
  129. // 恢复火狐及Safari浏览器下的图片拖拽
  130. document.ondragstart = null;
  131. }
  132. // 图片复原
  133. function resume() {
  134. initState();
  135. }
  136. // 上一页下一页
  137. function handleChange(direction: 'left' | 'right') {
  138. const { currentIndex } = imgState;
  139. const { imageList } = props;
  140. if (direction === 'left') {
  141. imgState.currentIndex--;
  142. if (currentIndex <= 0) {
  143. imgState.currentIndex = imageList.length - 1;
  144. }
  145. }
  146. if (direction === 'right') {
  147. imgState.currentIndex++;
  148. if (currentIndex >= imageList.length - 1) {
  149. imgState.currentIndex = 0;
  150. }
  151. }
  152. handleIChangeImage(imageList[imgState.currentIndex]);
  153. }
  154. function handleAddMoveListener(e: MouseEvent) {
  155. e = e || window.event;
  156. imgState.moveX = e.clientX;
  157. imgState.moveY = e.clientY;
  158. const imgEl = unref(imgElRef);
  159. if (imgEl) {
  160. imgEl.onmousemove = moveFunc;
  161. }
  162. }
  163. function moveFunc(e: MouseEvent) {
  164. e = e || window.event;
  165. e.preventDefault();
  166. const movementX = e.clientX - imgState.moveX;
  167. const movementY = e.clientY - imgState.moveY;
  168. imgState.imgLeft += movementX;
  169. imgState.imgTop += movementY;
  170. imgState.moveX = e.clientX;
  171. imgState.moveY = e.clientY;
  172. }
  173. // 获取图片样式
  174. const getImageStyle = computed(() => {
  175. const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
  176. return {
  177. transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
  178. marginTop: `${imgTop}px`,
  179. marginLeft: `${imgLeft}px`,
  180. };
  181. });
  182. const getIsMultipleImage = computed(() => {
  183. const { imageList } = props;
  184. return imageList.length > 1;
  185. });
  186. watchEffect(() => {
  187. if (props.show) {
  188. init();
  189. }
  190. if (props.imageList) {
  191. initState();
  192. }
  193. });
  194. const renderClose = () => {
  195. return (
  196. <div class={`${prefixCls}__close`} onClick={handleClose}>
  197. <CloseOutlined class={`${prefixCls}__close-icon`} />
  198. </div>
  199. );
  200. };
  201. const renderIndex = () => {
  202. if (!unref(getIsMultipleImage)) {
  203. return null;
  204. }
  205. const { currentIndex } = imgState;
  206. const { imageList } = props;
  207. return (
  208. <div class={`${prefixCls}__index`}>
  209. {currentIndex + 1} / {imageList.length}
  210. </div>
  211. );
  212. };
  213. const renderController = () => {
  214. return (
  215. <div class={`${prefixCls}__controller`}>
  216. <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
  217. <img src={unScaleSvg} />
  218. </div>
  219. <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
  220. <img src={scaleSvg} />
  221. </div>
  222. <div class={`${prefixCls}__controller-item`} onClick={resume}>
  223. <img src={resumeSvg} />
  224. </div>
  225. <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
  226. <img src={unRotateSvg} />
  227. </div>
  228. <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
  229. <img src={rotateSvg} />
  230. </div>
  231. </div>
  232. );
  233. };
  234. const renderArrow = (direction: 'left' | 'right') => {
  235. if (!unref(getIsMultipleImage)) {
  236. return null;
  237. }
  238. return (
  239. <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
  240. {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
  241. </div>
  242. );
  243. };
  244. return () => {
  245. return (
  246. imgState.show && (
  247. <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
  248. <div class={`${prefixCls}-content`}>
  249. {/*<Spin*/}
  250. {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
  251. {/* spinning={true}*/}
  252. {/* class={[*/}
  253. {/* `${prefixCls}-image`,*/}
  254. {/* {*/}
  255. {/* hidden: imgState.status !== StatueEnum.LOADING,*/}
  256. {/* },*/}
  257. {/* ]}*/}
  258. {/*/>*/}
  259. <img
  260. style={unref(getImageStyle)}
  261. class={[`${prefixCls}-image`, imgState.status === StatueEnum.DONE ? '' : 'hidden']}
  262. ref={imgElRef}
  263. src={imgState.currentUrl}
  264. onMousedown={handleAddMoveListener}
  265. />
  266. {renderClose()}
  267. {renderIndex()}
  268. {renderController()}
  269. {renderArrow('left')}
  270. {renderArrow('right')}
  271. </div>
  272. </div>
  273. )
  274. );
  275. };
  276. },
  277. });