diff --git a/components/image/demo/preview-src.vue b/components/image/demo/preview-src.vue
index 2b2d4629d9..f74bde67eb 100644
--- a/components/image/demo/preview-src.vue
+++ b/components/image/demo/preview-src.vue
@@ -8,20 +8,44 @@ title:
## zh-CN
-可以设置不同的预览图片。
+可以设置不同的预览图片。可以自定义预览图的加载占位符(placeholder)。
## en-US
-You can set different preview image.
+You can set different preview image. You can also customize the loading placeholder of preview image with VNode.
+
+
+
diff --git a/components/image/index.en-US.md b/components/image/index.en-US.md
index 5200aba318..34642b8bde 100644
--- a/components/image/index.en-US.md
+++ b/components/image/index.en-US.md
@@ -19,12 +19,12 @@ Previewable image.
| --- | --- | --- | --- | --- |
| alt | Image description | string | - | 2.0.0 |
| fallback | Load failure fault-tolerant src | string | - | 2.0.0 |
-| height | Image height | string \| number | - | 2.0.0 |
-| placeholder | Load placeholder, use default placeholder when set `true` | boolean \| slot | - | 2.0.0 |
-| preview | preview config, disabled when `false` | boolean \| [previewType](#previewtype) | true | 2.0.0 |
+| height | Image height | string\| number | - | 2.0.0 |
+| placeholder | Load placeholder, use default placeholder when set `true` | boolean\| slot | - | 2.0.0 |
+| preview | preview config, disabled when `false` | boolean\| [previewType](#previewtype) | true | 2.0.0 |
| src | Image path | string | - | 2.0.0 |
-| previewMask | custom mask | false \| function \| slot | - | 3.2.0 |
-| width | Image width | string \| number | - | 2.0.0 |
+| previewMask | custom mask | false\| function \| slot | - | 3.2.0 |
+| width | Image width | string\| number | - | 2.0.0 |
### events
@@ -38,11 +38,12 @@ Previewable image.
{
visible?: boolean;
onVisibleChange?: (visible, prevVisible) => void;
- getContainer?: string | HTMLElement | (() => HTMLElement);
+ getContainer: string | HTMLElement | (() => HTMLElement);
src?: string;
maskClassName?: string;
current?: number;
+ placeholder?: (() => vNode) | boolean;
}
```
-Other attributes [<img>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)
+Other attributes [<img>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)
diff --git a/components/image/index.zh-CN.md b/components/image/index.zh-CN.md
index b5a2f148f7..28bc91f113 100644
--- a/components/image/index.zh-CN.md
+++ b/components/image/index.zh-CN.md
@@ -20,12 +20,12 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*LVQ3R5JjjJEAAA
| --- | --- | --- | --- | --- |
| alt | 图像描述 | string | - | 2.0.0 |
| fallback | 加载失败容错地址 | string | - | 2.0.0 |
-| height | 图像高度 | string \| number | - | 2.0.0 |
-| placeholder | 加载占位, 为 `true` 时使用默认占位 | boolean \| slot | - | 2.0.0 |
-| preview | 预览参数,为 `false` 时禁用 | boolean \| [previewType](#previewtype) | true | 2.0.0 |
+| height | 图像高度 | string\| number | - | 2.0.0 |
+| placeholder | 加载占位, 为 `true` 时使用默认占位 | boolean\| slot | - | 2.0.0 |
+| preview | 预览参数,为 `false` 时禁用 | boolean\| [previewType](#previewtype) | true | 2.0.0 |
| src | 图片地址 | string | - | 2.0.0 |
-| previewMask | 自定义 mask | false \| function \| slot | - | 3.2.0 |
-| width | 图像宽度 | string \| number | - | 2.0.0 |
+| previewMask | 自定义 mask | false\| function \| slot | - | 3.2.0 |
+| width | 图像宽度 | string\| number | - | 2.0.0 |
### 事件
@@ -43,7 +43,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*LVQ3R5JjjJEAAA
src?: string;
maskClassName?: string;
current?: number;
+ placeholder?: (() => vNode) | boolean;
}
```
-其他属性见 [<img>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)
+其他属性见 [<img>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)
diff --git a/components/image/style/index.ts b/components/image/style/index.ts
index 8433f90dd7..cdf0d00a63 100644
--- a/components/image/style/index.ts
+++ b/components/image/style/index.ts
@@ -193,6 +193,9 @@ export const genImagePreviewStyle: GenerateStyle = (token: ImageToke
transition: `transform ${motionDurationSlow} ${motionEaseOut} 0s`,
userSelect: 'none',
pointerEvents: 'auto',
+ '&-placeholder': {
+ position: 'absolute',
+ },
'&-wrapper': {
...genBoxStyle(),
@@ -214,7 +217,6 @@ export const genImagePreviewStyle: GenerateStyle = (token: ImageToke
},
},
},
-
[`${previewCls}-moving`]: {
[`${previewCls}-preview-img`]: {
cursor: 'grabbing',
diff --git a/components/vc-image/src/Image.tsx b/components/vc-image/src/Image.tsx
index 643708dc52..0aa53c1bb8 100644
--- a/components/vc-image/src/Image.tsx
+++ b/components/vc-image/src/Image.tsx
@@ -1,4 +1,4 @@
-import type { CSSProperties, PropType } from 'vue';
+import type { CSSProperties, PropType, VNode } from 'vue';
import { ref, watch, defineComponent, computed, onMounted, onUnmounted } from 'vue';
import isNumber from 'lodash-es/isNumber';
import cn from '../../_util/classNames';
@@ -19,6 +19,8 @@ export type ImagePreviewType = Omit<
> & {
src?: string;
visible?: boolean;
+ fallback?: string;
+ placeholder?: boolean | (() => VNode);
onVisibleChange?: (value: boolean, prevValue: boolean) => void;
getContainer?: GetContainer | false;
maskClassName?: string;
@@ -79,6 +81,7 @@ const ImageInternal = defineComponent({
onVisibleChange: () => {},
getContainer: undefined,
};
+
return typeof props.preview === 'object'
? mergeDefaultValue(props.preview, defaultValues)
: defaultValues;
@@ -288,6 +291,7 @@ const ImageInternal = defineComponent({
onClose={onPreviewClose}
mousePosition={mousePosition.value}
src={mergedSrc}
+ placeholder={preview.value.placeholder}
alt={alt}
getContainer={getPreviewContainer.value}
icons={icons}
diff --git a/components/vc-image/src/Preview.tsx b/components/vc-image/src/Preview.tsx
index 80d03fc70e..36efc1b15a 100644
--- a/components/vc-image/src/Preview.tsx
+++ b/components/vc-image/src/Preview.tsx
@@ -7,11 +7,14 @@ import {
shallowRef,
watch,
cloneVNode,
+ ref,
+ nextTick,
} from 'vue';
-import type { VNode, PropType } from 'vue';
-
+import type { VNode, Ref, PropType } from 'vue';
+import type { ImageStatus } from './Image';
import classnames from '../../_util/classNames';
import Dialog from '../../vc-dialog';
+import Spin from '../../spin';
import { type IDialogChildProps, dialogPropTypes } from '../../vc-dialog/IDialogPropTypes';
import { getOffset } from '../../vc-util/Dom/css';
import addEventListener from '../../vc-util/Dom/addEventListener';
@@ -28,6 +31,7 @@ export interface PreviewProps extends Omit VNode);
icons?: {
rotateLeft?: VNode;
rotateRight?: VNode;
@@ -49,6 +53,11 @@ export const previewProps = {
...dialogPropTypes(),
src: String,
alt: String,
+ fallback: String,
+ placeholder: {
+ type: [Boolean, Function] as PropType VNode)>,
+ default: () => true,
+ },
rootClassName: String,
icons: {
type: Object as PropType,
@@ -65,7 +74,25 @@ const Preview = defineComponent({
const { rotateLeft, rotateRight, zoomIn, zoomOut, close, left, right, flipX, flipY } = reactive(
props.icons,
);
+ // 判断是否是自定义placeholder
+ const isCustomPlaceholder = computed(() => typeof props.placeholder === 'function');
+
+ const isDefaultPlaceholder = computed(() => {
+ return props.placeholder === true;
+ });
+ const hasPlaceholder = computed(() => isCustomPlaceholder.value || isDefaultPlaceholder.value);
+ const status: Ref = ref(hasPlaceholder.value ? 'loading' : 'normal');
+ watch(
+ () => props.src,
+ () => {
+ status.value = 'loading';
+ },
+ );
+ const renderPlaceholder = () => {
+ const { placeholder } = props;
+ return typeof placeholder === 'function' ? placeholder() : placeholder;
+ };
const scale = shallowRef(1);
const rotate = shallowRef(0);
const flip = reactive({ x: 1, y: 1 });
@@ -386,6 +413,11 @@ const Preview = defineComponent({
{
+ nextTick(() => {
+ status.value = 'normal';
+ });
+ }}
ref={imgRef}
class={`${props.prefixCls}-img`}
src={combinationSrc.value}
@@ -396,6 +428,12 @@ const Preview = defineComponent({
}deg)`,
}}
/>
+ {isDefaultPlaceholder.value && status.value === 'loading' && (
+
+ )}
+ {isCustomPlaceholder.value && status.value === 'loading' && (
+ {renderPlaceholder()}
+ )}
{showLeftOrRightSwitches.value && (