| | |
| | | type: Number as PropType<number>, |
| | | default: 0, |
| | | }, |
| | | scaleStep: { |
| | | type: Number as PropType<number>, |
| | | }, |
| | | defaultWidth: { |
| | | type: Number as PropType<number>, |
| | | }, |
| | | maskClosable: { |
| | | type: Boolean as PropType<boolean>, |
| | | }, |
| | | rememberState: { |
| | | type: Boolean as PropType<boolean>, |
| | | }, |
| | | }; |
| | | |
| | | const prefixCls = 'img-preview'; |
| | | export default defineComponent({ |
| | | name: 'ImagePreview', |
| | | props, |
| | | setup(props: Props) { |
| | | emits: ['img-load', 'img-error'], |
| | | setup(props: Props, { expose, emit }) { |
| | | interface stateInfo { |
| | | scale: number; |
| | | rotate: number; |
| | | top: number; |
| | | left: number; |
| | | } |
| | | const stateMap = new Map<string, stateInfo>(); |
| | | const imgState = reactive<ImgState>({ |
| | | currentUrl: '', |
| | | imgScale: 1, |
| | |
| | | }; |
| | | } |
| | | |
| | | const getScaleStep = computed(() => { |
| | | if (props.scaleStep > 0 && props.scaleStep < 100) { |
| | | return props.scaleStep / 100; |
| | | } else { |
| | | return imgState.imgScale / 10; |
| | | } |
| | | }); |
| | | |
| | | // 监听鼠标滚轮 |
| | | function scrollFunc(e: any) { |
| | | e = e || window.event; |
| | |
| | | e.preventDefault(); |
| | | if (e.delta > 0) { |
| | | // 滑轮向上滚动 |
| | | scaleFunc(0.015); |
| | | scaleFunc(getScaleStep.value); |
| | | } |
| | | if (e.delta < 0) { |
| | | // 滑轮向下滚动 |
| | | scaleFunc(-0.015); |
| | | scaleFunc(-getScaleStep.value); |
| | | } |
| | | } |
| | | // 缩放函数 |
| | |
| | | imgState.status = StatueEnum.LOADING; |
| | | const img = new Image(); |
| | | img.src = url; |
| | | img.onload = () => { |
| | | img.onload = (e: Event) => { |
| | | if (imgState.currentUrl !== url) { |
| | | const ele: HTMLElement[] = e.composedPath(); |
| | | if (props.rememberState) { |
| | | // 保存当前图片的缩放信息 |
| | | stateMap.set(imgState.currentUrl, { |
| | | scale: imgState.imgScale, |
| | | top: imgState.imgTop, |
| | | left: imgState.imgLeft, |
| | | rotate: imgState.imgRotate, |
| | | }); |
| | | // 如果之前已存储缩放信息,就应用 |
| | | const stateInfo = stateMap.get(url); |
| | | if (stateInfo) { |
| | | imgState.imgScale = stateInfo.scale; |
| | | imgState.imgTop = stateInfo.top; |
| | | imgState.imgRotate = stateInfo.rotate; |
| | | imgState.imgLeft = stateInfo.left; |
| | | } else { |
| | | initState(); |
| | | if (props.defaultWidth) { |
| | | imgState.imgScale = props.defaultWidth / ele[0].naturalWidth; |
| | | } |
| | | } |
| | | } else { |
| | | if (props.defaultWidth) { |
| | | imgState.imgScale = props.defaultWidth / ele[0].naturalWidth; |
| | | } |
| | | } |
| | | |
| | | ele && |
| | | emit('img-load', { |
| | | index: imgState.currentIndex, |
| | | dom: ele[0] as HTMLImageElement, |
| | | url, |
| | | }); |
| | | } |
| | | imgState.currentUrl = url; |
| | | imgState.status = StatueEnum.DONE; |
| | | }; |
| | | img.onerror = () => { |
| | | img.onerror = (e: Event) => { |
| | | const ele: EventTarget[] = e.composedPath(); |
| | | ele && |
| | | emit('img-error', { |
| | | index: imgState.currentIndex, |
| | | dom: ele[0] as HTMLImageElement, |
| | | url, |
| | | }); |
| | | imgState.status = StatueEnum.FAIL; |
| | | }; |
| | | } |
| | |
| | | // 关闭 |
| | | function handleClose(e: MouseEvent) { |
| | | e && e.stopPropagation(); |
| | | close(); |
| | | } |
| | | |
| | | function close() { |
| | | imgState.show = false; |
| | | // 移除火狐浏览器下的鼠标滚动事件 |
| | | document.body.removeEventListener('DOMMouseScroll', scrollFunc); |
| | |
| | | function resume() { |
| | | initState(); |
| | | } |
| | | |
| | | expose({ |
| | | resume, |
| | | close, |
| | | prev: handleChange.bind(null, 'left'), |
| | | next: handleChange.bind(null, 'right'), |
| | | setScale: (scale: number) => { |
| | | if (scale > 0 && scale <= 10) imgState.imgScale = scale; |
| | | }, |
| | | setRotate: (rotate: number) => { |
| | | imgState.imgRotate = rotate; |
| | | }, |
| | | } as PreviewActions); |
| | | |
| | | // 上一页下一页 |
| | | function handleChange(direction: 'left' | 'right') { |
| | |
| | | transform: `scale(${imgScale}) rotate(${imgRotate}deg)`, |
| | | marginTop: `${imgTop}px`, |
| | | marginLeft: `${imgLeft}px`, |
| | | maxWidth: props.defaultWidth ? 'unset' : '100%', |
| | | }; |
| | | }); |
| | | |
| | |
| | | initState(); |
| | | } |
| | | }); |
| | | |
| | | const handleMaskClick = (e: MouseEvent) => { |
| | | if ( |
| | | props.maskClosable && |
| | | e.target && |
| | | (e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`) |
| | | ) { |
| | | handleClose(e); |
| | | } |
| | | }; |
| | | |
| | | const renderClose = () => { |
| | | return ( |
| | |
| | | const renderController = () => { |
| | | return ( |
| | | <div class={`${prefixCls}__controller`}> |
| | | <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}> |
| | | <div |
| | | class={`${prefixCls}__controller-item`} |
| | | onClick={() => scaleFunc(-getScaleStep.value)} |
| | | > |
| | | <img src={unScaleSvg} /> |
| | | </div> |
| | | <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}> |
| | | <div |
| | | class={`${prefixCls}__controller-item`} |
| | | onClick={() => scaleFunc(getScaleStep.value)} |
| | | > |
| | | <img src={scaleSvg} /> |
| | | </div> |
| | | <div class={`${prefixCls}__controller-item`} onClick={resume}> |
| | |
| | | return () => { |
| | | return ( |
| | | imgState.show && ( |
| | | <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}> |
| | | <div |
| | | class={prefixCls} |
| | | ref={wrapElRef} |
| | | onMouseup={handleMouseUp} |
| | | onClick={handleMaskClick} |
| | | > |
| | | <div class={`${prefixCls}-content`}> |
| | | {/*<Spin*/} |
| | | {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/} |