-
Notifications
You must be signed in to change notification settings - Fork 69
feat(image-viewer): add image-viewer #607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
4ae42ec
feat(image-viewer): add image-viewer
novlan1 3f7ab77
feat(image-viewer): add transition
novlan1 13d67b4
feat(image-viewer): try add drag events
novlan1 50c1fb1
chore: update spell
novlan1 03830d7
chore: resolve conflict
novlan1 96a9eee
chore: merge develop
novlan1 e451ab0
feat(image-viewer): optimize viewer
novlan1 88435b7
chore: lint
novlan1 39c1c01
chore: update snapshot
novlan1 d1aeab0
chore: merge develop
github-actions[bot] 58a350a
chore: update snapshot
github-actions[bot] 86f58ac
feat(image-viewer): 优化preview
novlan1 c7a89ec
feat(image-viewer): 优化preview
novlan1 ee7483f
feat(image-viewer): 优化preview
novlan1 65ffbb9
feat(image-viewer): 优化preview
novlan1 e068d1e
feat(image-viewer): 优化preview
novlan1 29c0ccd
feat(image-viewer): 优化preview
novlan1 eb0e28e
feat(image-viewer): 优化preview
novlan1 dcaef5f
feat(image-viewer): 优化preview
novlan1 bf2c922
feat(image-viewer): 优化preview
novlan1 97e9887
feat(image-viewer): 优化preview
novlan1 8d620ca
feat(image-viewer): 优化preview
novlan1 8ca9d7c
feat(image-viewer): 优化preview
novlan1 5b35ada
feat(image-viewer): 优化preview
novlan1 c191015
feat(image-viewer): 优化preview
novlan1 493cd02
chore: test preview
novlan1 1f606ca
chore: test preview
novlan1 6d7328b
chore: test preview
novlan1 74673f5
chore: test preview
novlan1 7b0b7e9
feat(viewer): 优化放大
novlan1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React, { useState } from 'react'; | ||
import { ImageViewer, Button, type ImageInfo } from 'tdesign-mobile-react'; | ||
|
||
const images: ImageInfo[] = [ | ||
{ | ||
url: 'https://tdesign.gtimg.com/mobile/demos/swiper1.png', | ||
align: 'start', | ||
}, | ||
{ | ||
url: 'https://tdesign.gtimg.com/mobile/demos/swiper2.png', | ||
align: 'end', | ||
}, | ||
{ | ||
url: 'https://tdesign.gtimg.com/mobile/demos/swiper2.png', | ||
align: 'center', | ||
}, | ||
]; | ||
|
||
export default function AlignDemo() { | ||
const [visible, setVisible] = useState(false); | ||
|
||
return ( | ||
<div className="image-example"> | ||
<Button block size="large" variant="outline" theme="primary" onClick={() => setVisible(true)}> | ||
基础图片预览 + 对齐方式 | ||
</Button> | ||
|
||
<ImageViewer images={images} visible={visible} onClose={() => setVisible(false)} /> | ||
</div> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React, { useState } from 'react'; | ||
import { ImageViewer, Button } from 'tdesign-mobile-react'; | ||
|
||
const images = [ | ||
'https://tdesign.gtimg.com/mobile/demos/swiper1.png', | ||
'https://tdesign.gtimg.com/mobile/demos/swiper2.png', | ||
]; | ||
|
||
export default function BaseDemo() { | ||
const [visible, setVisible] = useState(false); | ||
|
||
return ( | ||
<div className="image-example"> | ||
<Button block size="large" variant="outline" theme="primary" onClick={() => setVisible(true)}> | ||
基础图片预览 | ||
</Button> | ||
|
||
<ImageViewer images={images} visible={visible} onClose={() => setVisible(false)} /> | ||
</div> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import React from 'react'; | ||
import TDemoBlock from '../../../site/mobile/components/DemoBlock'; | ||
import TDemoHeader from '../../../site/mobile/components/DemoHeader'; | ||
import BaseDemo from './base'; | ||
import AlignDemo from './align'; | ||
import OperationDemo from './operation'; | ||
import './style/index.less'; | ||
|
||
export default function ImageViewerDemo() { | ||
return ( | ||
<div className="tdesign-mobile-demo"> | ||
<TDemoHeader | ||
title="ImageViewer 图片预览" | ||
summary="图片全屏放大预览效果,包含全屏背景色、页码位置样式、增加操作等规范" | ||
/> | ||
<TDemoBlock title="01 组件类型" summary="图片预览类型" padding={true}> | ||
<BaseDemo /> | ||
</TDemoBlock> | ||
<TDemoBlock title="02 组件类型" summary="图片预览类型,可设置垂直对齐方式" padding={true}> | ||
<AlignDemo /> | ||
</TDemoBlock> | ||
<TDemoBlock title="03 组件类型" summary="带操作图片预览" padding={true}> | ||
<OperationDemo /> | ||
</TDemoBlock> | ||
</div> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import React, { useState } from 'react'; | ||
import { ImageViewer, Button } from 'tdesign-mobile-react'; | ||
|
||
const images = [ | ||
'https://tdesign.gtimg.com/mobile/demos/swiper1.png', | ||
'https://tdesign.gtimg.com/mobile/demos/swiper2.png', | ||
]; | ||
|
||
export default function OperationDemo() { | ||
const [visible, setVisible] = useState(false); | ||
|
||
return ( | ||
<div className="image-example"> | ||
<Button block size="large" variant="outline" theme="primary" onClick={() => setVisible(true)}> | ||
带操作图片预览 | ||
</Button> | ||
|
||
<ImageViewer | ||
images={images} | ||
visible={visible} | ||
showIndex={true} | ||
deleteBtn={true} | ||
onClose={() => setVisible(false)} | ||
/> | ||
</div> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.tdesign-mobile-demo { | ||
background-color: #fff; | ||
min-height: 100%; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/** | ||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC | ||
* */ | ||
|
||
import { TdImageViewerProps } from './type'; | ||
|
||
export const imageViewerDefaultProps: TdImageViewerProps = { | ||
closeBtn: true, | ||
deleteBtn: false, | ||
images: [], | ||
defaultIndex: 0, | ||
maxZoom: 3, | ||
showIndex: false, | ||
defaultVisible: false, | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
:: BASE_DOC :: | ||
|
||
## API | ||
|
||
### ImageViewer Props | ||
|
||
name | type | default | description | required | ||
-- | -- | -- | -- | -- | ||
className | String | - | className of component | N | ||
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N | ||
closeBtn | TNode | true | Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N | ||
deleteBtn | TNode | false | Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N | ||
images | Array | [] | Typescript:`Array<string \| ImageInfo>` `interface ImageInfo { url: string; align: 'start' \| 'center' \| 'end' }`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/image-viewer/type.ts) | N | ||
index | Number | 0 | \- | N | ||
defaultIndex | Number | 0 | uncontrolled property | N | ||
index | Number | - | \- | N | ||
defaultIndex | Number | - | uncontrolled property | N | ||
maxZoom | Number | 3 | Typescript:`number` | N | ||
showIndex | Boolean | false | \- | N | ||
visible | Boolean | false | hide or show image viewer | N | ||
defaultVisible | Boolean | false | hide or show image viewer。uncontrolled property | N | ||
onClose | Function | | Typescript:`(context: { trigger: 'overlay' \| 'close-btn', visible: boolean, index: number }) => void`<br/> | N | ||
onDelete | Function | | Typescript:`(index: number) => void`<br/> | N | ||
onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' \| 'current' }) => void`<br/> | N | ||
onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`<br/> | N |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
:: BASE_DOC :: | ||
|
||
## API | ||
|
||
### ImageViewer Props | ||
|
||
名称 | 类型 | 默认值 | 描述 | 必传 | ||
-- | -- | -- | -- | -- | ||
className | String | - | 类名 | N | ||
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N | ||
closeBtn | TNode | true | 是否展示关闭按钮,值为 `true` 显示默认关闭按钮;值为 `false` 则不显示关闭按钮;也可以完全自定义关闭按钮。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N | ||
deleteBtn | TNode | false | 是否显示删除操作,前提需要开启页码。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N | ||
images | Array | [] | 图片数组。TS 类型:`Array<string \| ImageInfo>` `interface ImageInfo { url: string; align: 'start' \| 'center' \| 'end' }`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/image-viewer/type.ts) | N | ||
index | Number | 0 | 当前预览图片所在的下标 | N | ||
defaultIndex | Number | 0 | 当前预览图片所在的下标。非受控属性 | N | ||
index | Number | - | 当前预览图片所在的下标 | N | ||
defaultIndex | Number | - | 当前预览图片所在的下标。非受控属性 | N | ||
maxZoom | Number | 3 | 【开发中】最大放大比例。TS 类型:`number` | N | ||
showIndex | Boolean | false | 是否显示页码 | N | ||
visible | Boolean | false | 隐藏/显示预览 | N | ||
defaultVisible | Boolean | false | 隐藏/显示预览。非受控属性 | N | ||
onClose | Function | | TS 类型:`(context: { trigger: 'overlay' \| 'close-btn', visible: boolean, index: number }) => void`<br/>关闭时触发 | N | ||
onDelete | Function | | TS 类型:`(index: number) => void`<br/>点击删除操作按钮时触发 | N | ||
onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' \| 'current' }) => void`<br/>预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N | ||
onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`<br/>预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import React, { MouseEvent, useRef, useState } from 'react'; | ||
import { CloseIcon, DeleteIcon } from 'tdesign-icons-react'; | ||
import { TdImageViewerProps, ImageInfo } from './type'; | ||
import { StyledProps } from '../common'; | ||
import { usePrefixClass } from '../hooks/useClass'; | ||
import useDefault from '../_util/useDefault'; | ||
import noop from '../_util/noop'; | ||
import useDefaultProps from '../hooks/useDefaultProps'; | ||
import { imageViewerDefaultProps } from './defaultProps'; | ||
import { Swiper as TSwiper } from '../swiper'; | ||
import parseTNode from '../_util/parseTNode'; | ||
import TSwiperItem from '../swiper/SwiperItem'; | ||
|
||
export interface ImageViewerProps extends TdImageViewerProps, StyledProps {} | ||
|
||
const ImageViewer: React.FC<ImageViewerProps> = (props) => { | ||
const { visible, defaultVisible, onClose, index, defaultIndex, onIndexChange, closeBtn, deleteBtn, onDelete } = | ||
useDefaultProps<ImageViewerProps>(props, imageViewerDefaultProps); | ||
|
||
const [show, setShow] = useDefault<boolean, any>(visible, defaultVisible, noop); | ||
const [currentIndex, setCurrentIndex] = useDefault(index, defaultIndex, onIndexChange); | ||
const [innerState, setInnerState] = useState({ | ||
dblTapZooming: false, // double tap zooming | ||
anlyyao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
zooming: false, // pinch zooming | ||
anlyyao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
scale: 1, | ||
touchIndex: 0, | ||
dragging: false, | ||
draggedX: 0, | ||
draggedY: 0, | ||
extraDraggedX: 0, | ||
}); | ||
|
||
const swiperRootRef = useRef<HTMLDivElement>(null); | ||
const rootRef = useRef<HTMLDivElement>(null); | ||
|
||
const beforeClose = () => { | ||
setInnerState({ | ||
...innerState, | ||
dblTapZooming: false, | ||
zooming: false, | ||
scale: 1, | ||
dragging: false, | ||
draggedX: 0, | ||
draggedY: 0, | ||
extraDraggedX: 0, | ||
}); | ||
}; | ||
|
||
const imageViewerClass = usePrefixClass('image-viewer'); | ||
const handleClose = (e: MouseEvent, trigger: 'overlay' | 'close-btn') => { | ||
beforeClose(); | ||
setShow(false); | ||
onClose?.({ trigger, visible: false, index }); | ||
}; | ||
const handleDelete = () => { | ||
onDelete?.(currentIndex ?? 0); | ||
}; | ||
|
||
const getPreloadImageIndex = () => { | ||
const lastIndex = props.images.length - 1; | ||
if ([undefined, 0].includes(currentIndex)) { | ||
return [0, 1, lastIndex]; | ||
} | ||
if (currentIndex === lastIndex) { | ||
return [lastIndex, lastIndex - 1, 0]; | ||
} | ||
const prev = currentIndex - 1 >= 0 ? currentIndex - 1 : lastIndex; | ||
const next = currentIndex + 1 <= lastIndex ? currentIndex + 1 : 0; | ||
return [currentIndex, prev, next]; | ||
}; | ||
|
||
const preloadImageIndex = getPreloadImageIndex(); | ||
|
||
const imageInfoList = props.images.map((image, index) => { | ||
let imageInfo: ImageInfo; | ||
if (typeof image === 'string') { | ||
imageInfo = { | ||
url: image, | ||
align: 'center', | ||
}; | ||
} else { | ||
imageInfo = image; | ||
} | ||
return { | ||
image: imageInfo, | ||
preload: preloadImageIndex.includes(index), | ||
}; | ||
}); | ||
|
||
const onSwiperChange = (index: number) => { | ||
if (currentIndex !== index) { | ||
const trigger = currentIndex < index ? 'next' : 'prev'; | ||
setCurrentIndex(index, { trigger }); | ||
} | ||
}; | ||
|
||
const getImageTransform = () => { | ||
const { scale, draggedX, draggedY } = innerState; | ||
return `matrix(${scale}, 0, 0, ${scale}, ${draggedX}, ${draggedY})`; | ||
}; | ||
const getImageTransitionDuration = () => { | ||
const { zooming, dragging } = innerState; | ||
return zooming || dragging ? { transitionDuration: '0s' } : { transitionDuration: '0.3s' }; | ||
}; | ||
|
||
return ( | ||
show && ( | ||
anlyyao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<div ref={rootRef} className={imageViewerClass}> | ||
<div className={`${imageViewerClass}__mask`} onClick={(e) => handleClose(e, 'overlay')}></div> | ||
<TSwiper | ||
ref={swiperRootRef} | ||
autoplay={false} | ||
height={'100vh'} | ||
className={`${imageViewerClass}__content`} | ||
onChange={onSwiperChange} | ||
current={currentIndex} | ||
defaultCurrent={currentIndex} | ||
> | ||
{imageInfoList.map((info, index) => ( | ||
<TSwiperItem | ||
key={index} | ||
className={`${imageViewerClass}__swiper-item`} | ||
style={{ touchAction: 'none', alignItems: info.image.align, display: 'flex' }} | ||
> | ||
<img | ||
src={info.image.url} | ||
style={{ | ||
transform: index === innerState.touchIndex ? getImageTransform() : 'matrix(1, 0, 0, 1, 0, 0)', | ||
...getImageTransitionDuration(), | ||
height: '100%', | ||
}} | ||
className={`${imageViewerClass}__img`} | ||
/> | ||
</TSwiperItem> | ||
))} | ||
</TSwiper> | ||
|
||
<div className={`${imageViewerClass}__nav`}> | ||
<div className={`${imageViewerClass}__nav-close`} onClick={(e) => handleClose(e, 'close-btn')}> | ||
{parseTNode(closeBtn, null, <CloseIcon />)} | ||
</div> | ||
|
||
{props.showIndex && ( | ||
<div className={`${imageViewerClass}__nav-index`}> | ||
{`${Math.min((currentIndex ?? 0) + 1, props.images?.length)}/${props.images?.length}`} | ||
</div> | ||
)} | ||
|
||
<div className={`${imageViewerClass}__nav-delete`} onClick={handleDelete}> | ||
{parseTNode(deleteBtn, null, <DeleteIcon />)} | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
); | ||
}; | ||
|
||
ImageViewer.displayName = 'ImageViewer'; | ||
|
||
export default ImageViewer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import _ImageViewer from './image-viewer'; | ||
|
||
import './style/index.js'; | ||
|
||
export type { ImageViewerProps } from './image-viewer'; | ||
export * from './type'; | ||
|
||
export const ImageViewer = _ImageViewer; | ||
export default ImageViewer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import './index.css'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import '../../_common/style/mobile/components/image-viewer/v2/_index.less'; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.