Skip to content

Commit

Permalink
feat(image): support .avif and .webp (#2463)
Browse files Browse the repository at this point in the history
  • Loading branch information
chaishi authored Feb 26, 2023
1 parent d7867e2 commit e8c06eb
Show file tree
Hide file tree
Showing 15 changed files with 262 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/_common
9 changes: 9 additions & 0 deletions src/config-provider/_example/others.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
<t-step-item title="Last Step" content="You haven't finish this step."></t-step-item>
</t-steps>
<br /><br />

<t-image src="1.jpg" fit="scale-down" style="width: 300px"></t-image>
</t-config-provider>
</template>

Expand All @@ -102,6 +104,13 @@ for (let i = 0; i < 20; i++) {
// 全局特性配置,可以引入英文默认配置 enConfig,还可以在默认配置的基础上进行自定义配置
const globalConfig = merge(enConfig, {
image: {
// 全局替换图片地址
replaceImageSrc(params) {
console.log(params);
return 'https://tdesign.gtimg.com/demo/demo-image-1.png';
},
},
form: {
requiredMark: false,
},
Expand Down
3 changes: 2 additions & 1 deletion src/config-provider/config-provider.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,15 @@ copyText | String | - | \- | N

name | type | default | description | required
-- | -- | -- | -- | --
`MessageOptions` | \- | - | \- | N
`MessageOptions` | \- | - | extends `MessageOptions` | N

### ImageConfig

name | type | default | description | required
-- | -- | -- | -- | --
errorText | String | - | loading text, default value is "Error" | N
loadingText | String | - | loading text, default value is "loading" | N
replaceImageSrc | Function | - | replace all `src` attribute of images。Typescript:`(params: ImageProps) => string`[Image API Documents](./image?tab=api)[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/config-provider/type.ts) | N

### ImageViewerConfig

Expand Down
3 changes: 2 additions & 1 deletion src/config-provider/config-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,15 @@ copyText | String | - | 语言配置,“复制链接” 描述文本 | N

名称 | 类型 | 默认值 | 说明 | 必传
-- | -- | -- | -- | --
`MessageOptions` | \- | - | 继承 `MessageOptions` 中的全部 API | N
`MessageOptions` | \- | - | 继承 `MessageOptions` 中的全部属性 | N

### ImageConfig

名称 | 类型 | 默认值 | 说明 | 必传
-- | -- | -- | -- | --
errorText | String | - | 图片加载失败显示的文本,中文默认为“图片无法显示” | N
loadingText | String | - | 图片加载中显示的文本,中文默认为“图片加载中” | N
replaceImageSrc | Function | - | 统一替换图片 `src` 地址,参数为组件的全部属性,返回值为新的图片地址。TS 类型:`(params: ImageProps) => string`[Image API Documents](./image?tab=api)[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/config-provider/type.ts) | N

### ImageViewerConfig

Expand Down
5 changes: 5 additions & 0 deletions src/config-provider/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CalendarController } from '../calendar';
import { ButtonProps } from '../button';
import { FormErrorMessage } from '../form';
import { MessageOptions } from '../message';
import { ImageProps } from '../image';
import { TNode } from '../common';

export interface GlobalConfigProvider {
Expand Down Expand Up @@ -833,6 +834,10 @@ export interface ImageConfig {
* @default ''
*/
loadingText?: string;
/**
* 统一替换图片 `src` 地址,参数为组件的全部属性,返回值为新的图片地址
*/
replaceImageSrc?: (params: ImageProps) => string;
}

export interface ImageViewerConfig {
Expand Down
2 changes: 1 addition & 1 deletion src/config-provider/useConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export * from './type';
*/
export function useConfig<T extends keyof GlobalConfigProvider>(componentName?: T) {
const injectGlobalConfig = getCurrentInstance() ? inject(configProviderInjectKey, null) : globalConfigCopy;
const mergedGlobalConfig = computed(() => injectGlobalConfig?.value || defaultGlobalConfig);
const mergedGlobalConfig = computed(() => injectGlobalConfig?.value || (defaultGlobalConfig as GlobalConfigProvider));
const globalConfig = computed(() => mergedGlobalConfig.value[componentName]);

const classPrefix = computed(() => {
Expand Down
15 changes: 15 additions & 0 deletions src/image/__tests__/vitest-image.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,21 @@ describe('Image Component', () => {
});
});

it(`props.srcset is equal to {'image/avif': 'https://tdesign.gtimg.com/img/tdesign-image.avif','image/webp': 'https://tdesign.gtimg.com/img/tdesign-image.webp'}`, () => {
const wrapper = mount(
<Image
srcset={{
'image/avif': 'https://tdesign.gtimg.com/img/tdesign-image.avif',
'image/webp': 'https://tdesign.gtimg.com/img/tdesign-image.webp',
}}
></Image>,
);
const domWrapper = wrapper.find('picture > source');
expect(domWrapper.attributes('srcset')).toBe('https://tdesign.gtimg.com/img/tdesign-image.avif');
const domWrapper1 = wrapper.find('picture > source:nth-child(2)');
expect(domWrapper1.attributes('srcset')).toBe('https://tdesign.gtimg.com/img/tdesign-image.webp');
});

it('events.error works fine', async () => {
const onErrorFn = vi.fn();
const wrapper = mount(<Image src={'https://load-failed-img.png'} onError={onErrorFn}></Image>);
Expand Down
15 changes: 15 additions & 0 deletions src/image/_example/avif.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<t-space direction="vertical" align="center">
<t-image
src="https://tdesign.gtimg.com/demo/demo-image-1.png"
:srcset="{
'image/avif': 'https://tdesign.gtimg.com/img/tdesign-image.avif',
'image/webp': 'https://tdesign.gtimg.com/img/tdesign-image.webp',
}"
shape="square"
:style="{ maxWidth: '100%' }"
fit="scale-down"
/>
<span>.avif / .webp</span>
</t-space>
</template>
1 change: 1 addition & 0 deletions src/image/image.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ placeholder | String / Slot / Function | - | Typescript:`string \| TNode`。[s
position | String | center | \- | N
shape | String | square | options:circle/round/square | N
src | String | - | \- | N
srcset | Object | - | for `.avif` and `.webp` image url。Typescript:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/image/type.ts) | N
onError | Function | | Typescript:`(context: { e: ImageEvent }) => void`<br/>trigger on image load failed | N
onLoad | Function | | Typescript:`(context: { e: ImageEvent }) => void`<br/>trigger on image loaded | N

Expand Down
1 change: 1 addition & 0 deletions src/image/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ placeholder | String / Slot / Function | - | 占位元素,展示层级低于 `
position | String | center | 等同于原生的 object-position 属性,可选值为 top right bottom left 或 string,可以自定义任何单位,px 或者 百分比 | N
shape | String | square | 图片圆角类型。可选项:circle/round/square | N
src | String | - | 图片链接 | N
srcset | Object | - | 图片地址,支持特殊格式的图片,如 `.avif``.webp`。TS 类型:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/image/type.ts) | N
onError | Function | | TS 类型:`(context: { e: ImageEvent }) => void`<br/>图片加载失败时触发 | N
onLoad | Function | | TS 类型:`(context: { e: ImageEvent }) => void`<br/>图片加载完成时触发 | N

Expand Down
44 changes: 29 additions & 15 deletions src/image/image.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineComponent, ref, onMounted, computed, onUnmounted, watch } from 'vue';
import omit from 'lodash/omit';
import isFunction from 'lodash/isFunction';
import { ImageErrorIcon, ImageIcon } from 'tdesign-icons-vue-next';
import observe from '../_common/js/utils/observe';
import { useConfig } from '../config-provider/useConfig';
Expand Down Expand Up @@ -32,13 +33,16 @@ export default defineComponent({

const { classPrefix, globalConfig } = useConfig('image');

const imageSrc = ref(props.src);
// replace image url
const imageSrc = computed(() =>
isFunction(globalConfig.value.replaceImageSrc) ? globalConfig.value.replaceImageSrc(props) : props.src,
);

watch(
() => props.src,
() => {
hasError.value = false;
isLoaded.value = false;
imageSrc.value = props.src;
},
);

Expand All @@ -63,6 +67,12 @@ export default defineComponent({
return props.overlayTrigger === 'hover';
});

const imageClasses = computed(() => [
`${classPrefix.value}-image`,
`${classPrefix.value}-image--fit-${props.fit}`,
`${classPrefix.value}-image--position-${props.position}`,
]);

const shouldShowOverlay = ref(!hasMouseEvent.value);
const handleToggleOverlay = () => {
if (hasMouseEvent.value) {
Expand Down Expand Up @@ -96,6 +106,21 @@ export default defineComponent({
);
};

function renderImageSrcset() {
return (
<picture>
{Object.entries(props.srcset).map(([type, url]) => (
<source type={type} srcset={url} />
))}
{props.src && renderImage(props.src)}
</picture>
);
}

function renderImage(url: string) {
return <img src={url} onError={handleError} onLoad={handleLoad} class={imageClasses.value} alt={props.alt} />;
}

const renderTNodDefault = useTNodeDefault();

return () => (
Expand Down Expand Up @@ -130,19 +155,8 @@ export default defineComponent({
{renderGalleryShadow()}

{(hasError.value || !shouldLoad.value) && <div class={`${classPrefix.value}-image`} />}
{!(hasError.value || !shouldLoad.value) && (
<img
src={imageSrc.value}
onError={handleError}
onLoad={handleLoad}
class={[
`${classPrefix.value}-image`,
`${classPrefix.value}-image--fit-${props.fit}`,
`${classPrefix.value}-image--position-${props.position}`,
]}
alt={props.alt}
/>
)}
{!(hasError.value || !shouldLoad.value) &&
(props.srcset && Object.keys(props.srcset).length ? renderImageSrcset() : renderImage(imageSrc.value))}
{!(hasError.value || !shouldLoad.value) && !isLoaded.value && (
<div class={`${classPrefix.value}-image__loading`}>
{renderTNodeJSX('loading') || (
Expand Down
4 changes: 4 additions & 0 deletions src/image/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export default {
type: String,
default: '',
},
/** 图片地址,支持特殊格式的图片,如 `.avif` 和 `.webp` */
srcset: {
type: Object as PropType<TdImageProps['srcset']>,
},
/** 图片加载失败时触发 */
onError: Function as PropType<TdImageProps['onError']>,
/** 图片加载完成时触发 */
Expand Down
9 changes: 9 additions & 0 deletions src/image/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export interface TdImageProps {
* @default ''
*/
src?: string;
/**
* 图片地址,支持特殊格式的图片,如 `.avif` 和 `.webp`
*/
srcset?: ImageSrcset;
/**
* 图片加载失败时触发
*/
Expand All @@ -72,3 +76,8 @@ export interface TdImageProps {
*/
onLoad?: (context: { e: ImageEvent }) => void;
}

export interface ImageSrcset {
'image/avif': string;
'image/webp': string;
}
Loading

0 comments on commit e8c06eb

Please sign in to comment.