Skip to content

Commit 2671988

Browse files
feat(image-viewer): add image-viewer (#607)
* feat(image-viewer): add image-viewer * feat(image-viewer): add transition * feat(image-viewer): try add drag events * chore: update spell * feat(image-viewer): optimize viewer * chore: lint * chore: update snapshot * chore: update snapshot * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * feat(image-viewer): 优化preview * chore: test preview * chore: test preview * chore: test preview * chore: test preview * feat(viewer): 优化放大 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent fb234c7 commit 2671988

27 files changed

+1273
-97
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"concurrently": "^6.4.0",
114114
"cross-env": "^7.0.3",
115115
"cssnano": "^5.0.12",
116+
"csstype": "^3.1.3",
116117
"cz-conventional-changelog": "^3.3.0",
117118
"dom-parser": "^0.1.6",
118119
"eslint": "^8.4.1",

site/mobile/mobile.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export default {
4545
name: 'image',
4646
component: () => import('tdesign-mobile-react/image/_example/index.tsx'),
4747
},
48+
{
49+
title: 'ImageViewer 图片预览',
50+
name: 'image-viewer',
51+
component: () => import('tdesign-mobile-react/image-viewer/_example/index.tsx'),
52+
},
4853
{
4954
title: 'Overlay 遮罩层',
5055
name: 'overlay',

site/web/site.config.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,14 @@ export const docs = [
384384
component: () => import('tdesign-mobile-react/image/image.md'),
385385
componentEn: () => import('tdesign-mobile-react/image/image.en-US.md'),
386386
},
387-
// {
388-
// title: 'ImageViewer 图片预览',
389-
// name: 'image-viewer',
390-
// path: '/mobile-react/components/image-viewer',
391-
// component: () => import('tdesign-mobile-react/image-viewer/image-viewer.md'),
392-
// },
387+
{
388+
title: 'ImageViewer 图片预览',
389+
titleEn: 'ImageViewer',
390+
name: 'ImageViewer',
391+
path: '/mobile-react/components/image-viewer',
392+
component: () => import('tdesign-mobile-react/image-viewer/image-viewer.md'),
393+
componentEn: () => import('tdesign-mobile-react/image-viewer/image-viewer.en-US.md'),
394+
},
393395
{
394396
title: 'List 列表',
395397
name: 'list',

src/_util/useSwipe.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export interface UseSwipeOptions {
3535
* 滑动结束时的回调函数
3636
*/
3737
onSwipeEnd?: (e: TouchEvent) => void;
38+
39+
/**
40+
* 是否禁用
41+
*/
42+
disabled?: boolean;
3843
}
3944

4045
/**
@@ -48,7 +53,7 @@ export function useSwipe(target: EventTarget | null | undefined, options = {} as
4853
const coordsStart = useRef<Position>({ x: 0, y: 0 }); // 用于存储触摸起始位置的坐标
4954
const coordsEnd = useRef<Position>({ x: 0, y: 0 }); // 用于存储触摸结束位置的坐标
5055
const coordsOffset = useRef<Position>({ x: 0, y: 0 }); // 用于存储滑动偏移量
51-
const { threshold = 0, onSwipe, onSwipeEnd, onSwipeStart, listenerOptions = { passive: true } } = options;
56+
const { threshold = 0, onSwipe, onSwipeEnd, onSwipeStart, listenerOptions = { passive: true }, disabled } = options;
5257

5358
const updateOffset = useCallback(() => {
5459
coordsOffset.current = {
@@ -95,6 +100,7 @@ export function useSwipe(target: EventTarget | null | undefined, options = {} as
95100
const onTouchStart = useCallback(
96101
(e: TouchEvent) => {
97102
if (e.touches.length !== 1) return;
103+
if (disabled) return;
98104
if (
99105
listenerOptions === true ||
100106
(isObject(listenerOptions) && listenerOptions.capture && listenerOptions.passive)
@@ -106,6 +112,7 @@ export function useSwipe(target: EventTarget | null | undefined, options = {} as
106112
updateCoordsEnd(x, y);
107113
onSwipeStart?.(e);
108114
},
115+
// eslint-disable-next-line react-hooks/exhaustive-deps
109116
[listenerOptions, updateCoordsStart, updateCoordsEnd, onSwipeStart],
110117
);
111118

src/image-viewer/_example/align.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { useState } from 'react';
2+
import { ImageViewer, Button, type ImageInfo } from 'tdesign-mobile-react';
3+
4+
const images: ImageInfo[] = [
5+
{
6+
url: 'https://tdesign.gtimg.com/mobile/demos/swiper1.png',
7+
align: 'start',
8+
},
9+
{
10+
url: 'https://tdesign.gtimg.com/mobile/demos/swiper2.png',
11+
align: 'end',
12+
},
13+
{
14+
url: 'https://tdesign.gtimg.com/mobile/demos/swiper2.png',
15+
align: 'center',
16+
},
17+
];
18+
19+
export default function AlignDemo() {
20+
const [visible, setVisible] = useState(false);
21+
22+
return (
23+
<div className="image-example">
24+
<Button block size="large" variant="outline" theme="primary" onClick={() => setVisible(true)}>
25+
基础图片预览 + 对齐方式
26+
</Button>
27+
28+
<ImageViewer images={images} visible={visible} onClose={() => setVisible(false)} />
29+
</div>
30+
);
31+
}

src/image-viewer/_example/base.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, { useState } from 'react';
2+
import { ImageViewer, Button } from 'tdesign-mobile-react';
3+
4+
const images = [
5+
'https://tdesign.gtimg.com/mobile/demos/swiper1.png',
6+
'https://tdesign.gtimg.com/mobile/demos/swiper2.png',
7+
];
8+
9+
export default function BaseDemo() {
10+
const [visible, setVisible] = useState(false);
11+
12+
const onIndexChange = (...args) => {
13+
console.log('[onIndexChange]', args);
14+
};
15+
16+
return (
17+
<div className="image-example">
18+
<Button block size="large" variant="outline" theme="primary" onClick={() => setVisible(true)}>
19+
基础图片预览
20+
</Button>
21+
22+
<ImageViewer images={images} visible={visible} onClose={() => setVisible(false)} onIndexChange={onIndexChange} />
23+
</div>
24+
);
25+
}

src/image-viewer/_example/index.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import TDemoBlock from '../../../site/mobile/components/DemoBlock';
3+
import TDemoHeader from '../../../site/mobile/components/DemoHeader';
4+
import BaseDemo from './base';
5+
import AlignDemo from './align';
6+
import OperationDemo from './operation';
7+
import './style/index.less';
8+
9+
export default function ImageViewerDemo() {
10+
return (
11+
<div className="tdesign-mobile-demo">
12+
<TDemoHeader
13+
title="ImageViewer 图片预览"
14+
summary="图片全屏放大预览效果,包含全屏背景色、页码位置样式、增加操作等规范"
15+
/>
16+
<TDemoBlock title="01 组件类型" summary="图片预览类型" padding={true}>
17+
<BaseDemo />
18+
</TDemoBlock>
19+
<TDemoBlock title="02 组件类型" summary="图片预览类型,可设置垂直对齐方式" padding={true}>
20+
<AlignDemo />
21+
</TDemoBlock>
22+
<TDemoBlock title="03 组件类型" summary="带操作图片预览" padding={true}>
23+
<OperationDemo />
24+
</TDemoBlock>
25+
</div>
26+
);
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { useState } from 'react';
2+
import { ImageViewer, Button } from 'tdesign-mobile-react';
3+
4+
const images = [
5+
'https://tdesign.gtimg.com/mobile/demos/swiper1.png',
6+
'https://tdesign.gtimg.com/mobile/demos/swiper2.png',
7+
];
8+
9+
export default function OperationDemo() {
10+
const [visible, setVisible] = useState(false);
11+
12+
return (
13+
<div className="image-example">
14+
<Button block size="large" variant="outline" theme="primary" onClick={() => setVisible(true)}>
15+
带操作图片预览
16+
</Button>
17+
18+
<ImageViewer
19+
images={images}
20+
visible={visible}
21+
showIndex={true}
22+
deleteBtn={true}
23+
onClose={() => setVisible(false)}
24+
/>
25+
</div>
26+
);
27+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.tdesign-mobile-demo {
2+
background-color: #fff;
3+
min-height: 100%;
4+
}

src/image-viewer/defaultProps.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
3+
* */
4+
5+
import { TdImageViewerProps } from './type';
6+
7+
export const imageViewerDefaultProps: TdImageViewerProps = {
8+
closeBtn: true,
9+
deleteBtn: false,
10+
images: [],
11+
defaultIndex: 0,
12+
maxZoom: 3,
13+
showIndex: false,
14+
defaultVisible: false,
15+
};
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { getClientSize } from './util';
2+
3+
function fixPoint(key: 'x' | 'y', start: number, width: number, clientWidth: number) {
4+
const startAddWidth = start + width;
5+
const offsetStart = key === 'x' ? (width - clientWidth) / 2 : 0;
6+
// const offsetEnd = key === 'x' ? -offsetStart : clientWidth - width;
7+
8+
if (width > clientWidth) {
9+
if (start > 0) {
10+
return {
11+
[key]: offsetStart,
12+
};
13+
}
14+
if (start < 0 && startAddWidth < clientWidth) {
15+
return {
16+
[key]: -offsetStart,
17+
};
18+
}
19+
} else if (start < 0 || startAddWidth > clientWidth) {
20+
return {
21+
[key]: start < 0 ? offsetStart : -offsetStart,
22+
};
23+
}
24+
return {};
25+
}
26+
27+
/**
28+
* Fix position x,y point when
29+
*
30+
* Ele width && height < client
31+
* - Back origin
32+
*
33+
* - Ele width | height > clientWidth | clientHeight
34+
* - left | top > 0 -> Back 0
35+
* - left | top + width | height < clientWidth | clientHeight -> Back left | top + width | height === clientWidth | clientHeight
36+
*
37+
* Regardless of other
38+
*/
39+
export default function getFixScaleEleTransPosition(
40+
width: number,
41+
height: number,
42+
left: number,
43+
top: number,
44+
): null | { x: number; y: number } {
45+
const { width: clientWidth, height: clientHeight } = getClientSize();
46+
47+
let fixPos = null;
48+
49+
if (width <= clientWidth && height <= clientHeight) {
50+
fixPos = {
51+
x: 0,
52+
y: 0,
53+
};
54+
} else if (width > clientWidth || height > clientHeight) {
55+
fixPos = {
56+
...fixPoint('x', left, width, clientWidth),
57+
...fixPoint('y', top, height, clientHeight),
58+
};
59+
}
60+
61+
return fixPos;
62+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
:: BASE_DOC ::
2+
3+
## API
4+
5+
### ImageViewer Props
6+
7+
name | type | default | description | required
8+
-- | -- | -- | -- | --
9+
className | String | - | className of component | N
10+
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
11+
closeBtn | TNode | true | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
12+
deleteBtn | TNode | false | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
13+
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
14+
index | Number | 0 | \- | N
15+
defaultIndex | Number | 0 | uncontrolled property | N
16+
maxZoom | Number | 3 | Typescript:`number` | N
17+
showIndex | Boolean | false | \- | N
18+
visible | Boolean | false | hide or show image viewer | N
19+
defaultVisible | Boolean | false | hide or show image viewer。uncontrolled property | N
20+
onClose | Function | | Typescript:`(context: { trigger: 'overlay' \| 'close-btn', visible: boolean, index: number }) => void`<br/> | N
21+
onDelete | Function | | Typescript:`(index: number) => void`<br/> | N
22+
onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`<br/> | N

src/image-viewer/image-viewer.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
:: BASE_DOC ::
2+
3+
## API
4+
5+
### ImageViewer Props
6+
7+
名称 | 类型 | 默认值 | 描述 | 必传
8+
-- | -- | -- | -- | --
9+
className | String | - | 类名 | N
10+
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
11+
closeBtn | TNode | true | 是否展示关闭按钮,值为 `true` 显示默认关闭按钮;值为 `false` 则不显示关闭按钮;也可以完全自定义关闭按钮。TS 类型:`boolean \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
12+
deleteBtn | TNode | false | 是否显示删除操作,前提需要开启页码。TS 类型:`boolean \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
13+
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
14+
index | Number | 0 | 当前预览图片所在的下标 | N
15+
defaultIndex | Number | 0 | 当前预览图片所在的下标。非受控属性 | N
16+
maxZoom | Number | 3 | 【开发中】最大放大比例。TS 类型:`number` | N
17+
showIndex | Boolean | false | 是否显示页码 | N
18+
visible | Boolean | false | 隐藏/显示预览 | N
19+
defaultVisible | Boolean | false | 隐藏/显示预览。非受控属性 | N
20+
onClose | Function | | TS 类型:`(context: { trigger: 'overlay' \| 'close-btn', visible: boolean, index: number }) => void`<br/>关闭时触发 | N
21+
onDelete | Function | | TS 类型:`(index: number) => void`<br/>点击删除操作按钮时触发 | N
22+
onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`<br/>预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N

0 commit comments

Comments
 (0)