Skip to content

Commit

Permalink
feat(components): [image] support native lazy loading (element-plus#7968
Browse files Browse the repository at this point in the history
)
  • Loading branch information
tolking authored Jun 1, 2022
1 parent 19aa8ca commit 60cd22b
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 61 deletions.
12 changes: 12 additions & 0 deletions breakings/2.2.3/image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- scope: 'component'
name: 'el-image'
type: 'props'
version: '2.2.3'
commit_hash: '7a48556'
description: |
Per [HTMLImageElement.loading Request](https://github.com/element-plus/element-plus/issues/7841),
Add [native loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading) support for Image components.
props:
- api: 'loading'
before: ''
after: '"eager" | "lazy"'
37 changes: 23 additions & 14 deletions docs/en-US/component/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ image/load-failed

## Lazy Load

:::tip

Native `loading` has been supported since <VersionTag version="2.2.3" />, you can use `loading = "lazy"` to replace `lazy = true`.

If the current browser supports native lazy loading, the native lazy loading will be used first, otherwise will be implemented through scroll.

:::

:::demo Use lazy load by `lazy = true`. Image will load until scroll into view when set. You can indicate scroll container that adds scroll listener to by `scroll-container`. If undefined, will be the nearest parent container whose overflow property is auto or scroll.

image/lazy-load
Expand All @@ -51,20 +59,21 @@ image/image-preview

### Image Attributes

| Name | Description | Type | Default |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------- |
| `src` | image source, same as native. | `string` ||
| `fit` | indicate how the image should be resized to fit its container, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). | `'fill' \| 'contain' \| 'cover' \| 'none' \| 'scale'-down'` ||
| `hide-on-click-modal` | when enabling preview, use this flag to control whether clicking on backdrop can exit preview mode. | `boolean` | `false` |
| `lazy` | whether to use lazy load. | `boolean` | `false` |
| `scroll-container` | the container to add scroll listener when using lazy load. | `string \| HTMLElement` | the nearest parent container whose overflow property is auto or scroll. |
| `alt` | native attribute `alt`. | `string` ||
| `referrer-policy` | native attribute `referrerPolicy`. | `string` ||
| `preview-src-list` | allow big image preview. | `string[]` ||
| `z-index` | set image preview z-index. | `number` ||
| `initial-index` | initial preview image index, less than the length of `url-list`. | `number` | `0` |
| `close-on-press-escape` | whether the image-viewer can be closed by pressing ESC | `boolean` | `true` |
| `preview-teleported` | whether to append image-viewer to body. A nested parent element attribute transform should have this attribute set to `true`. | `boolean` | `false` |
| Attribute | Description | Type | Default |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------- |
| `src` | image source, same as native. | `string` ||
| `fit` | indicate how the image should be resized to fit its container, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). | `'fill' \| 'contain' \| 'cover' \| 'none' \| 'scale'-down'` ||
| `hide-on-click-modal` | when enabling preview, use this flag to control whether clicking on backdrop can exit preview mode. | `boolean` | `false` |
| `loading` <VersionTag version="2.2.3" /> | Indicates how the browser should load the image, same as [native](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading) | `'eager' \| 'lazy'` ||
| `lazy` | whether to use lazy load. | `boolean` | `false` |
| `scroll-container` | the container to add scroll listener when using lazy load. | `string \| HTMLElement` | the nearest parent container whose overflow property is auto or scroll. |
| `alt` | native attribute `alt`. | `string` ||
| `referrer-policy` | native attribute `referrerPolicy`. | `string` ||
| `preview-src-list` | allow big image preview. | `string[]` ||
| `z-index` | set image preview z-index. | `number` ||
| `initial-index` | initial preview image index, less than the length of `url-list`. | `number` | `0` |
| `close-on-press-escape` | whether the image-viewer can be closed by pressing ESC | `boolean` | `true` |
| `preview-teleported` | whether to append image-viewer to body. A nested parent element attribute transform should have this attribute set to `true`. | `boolean` | `false` |

### Image Events

Expand Down
16 changes: 16 additions & 0 deletions packages/components/image/__tests__/image.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ describe('Image.vue', () => {
).not.toContain('display: none')
})

test('native loading attributes', async () => {
const wrapper = mount(Image, {
props: {
src: IMAGE_SUCCESS,
loading: 'eager',
} as ElImageProps,
})

await doubleWait()
expect(wrapper.find('img').exists()).toBe(true)
expect(wrapper.find('img').attributes('loading')).toBe('eager')

await wrapper.setProps({ loading: undefined })
expect(wrapper.find('img').attributes('loading')).toBe(undefined)
})

test('$attrs', async () => {
const alt = 'this ia alt'
const props: ElImageProps = {
Expand Down
4 changes: 4 additions & 0 deletions packages/components/image/src/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const imageProps = buildProps({
values: ['', 'contain', 'cover', 'fill', 'none', 'scale-down'],
default: '',
},
loading: {
type: String,
values: ['eager', 'lazy'],
},
lazy: {
type: Boolean,
default: false,
Expand Down
75 changes: 28 additions & 47 deletions packages/components/image/src/image.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
<template>
<div ref="container" :class="[ns.b(), $attrs.class]" :style="containerStyle">
<slot v-if="loading" name="placeholder">
<div :class="ns.e('placeholder')" />
</slot>
<slot v-else-if="hasLoadError" name="error">
<div :class="ns.e('error')">{{ t('el.image.error') }}</div>
</slot>
<img
v-else
v-if="imageSrc !== undefined && !hasLoadError"
v-bind="attrs"
:src="src"
:src="imageSrc"
:loading="loading"
:style="imageStyle"
:class="[ns.e('inner'), preview ? ns.e('preview') : '']"
@click="clickHandler"
@load="handleLoad"
@error="handleError"
/>
<slot v-if="isLoading" name="placeholder">
<div :class="ns.e('placeholder')" />
</slot>
<slot v-else-if="hasLoadError" name="error">
<div :class="ns.e('error')">{{ t('el.image.error') }}</div>
</slot>
<template v-if="preview">
<image-viewer
v-if="showViewer"
Expand Down Expand Up @@ -72,14 +75,14 @@ const ns = useNamespace('image')
const rawAttrs = useRawAttrs()
const attrs = useAttrs()
const imageSrc = ref<string | undefined>()
const hasLoadError = ref(false)
const loading = ref(true)
const imgWidth = ref(0)
const imgHeight = ref(0)
const isLoading = ref(true)
const showViewer = ref(false)
const container = ref<HTMLElement>()
const _scrollContainer = ref<HTMLElement | Window>()
const supportLoading = isClient && 'loading' in HTMLImageElement.prototype
let stopScrollListener: (() => void) | undefined
let stopWheelListener: (() => void) | undefined
Expand Down Expand Up @@ -107,49 +110,27 @@ const imageIndex = computed(() => {
return previewIndex
})
const isManual = computed(() => {
if (props.loading === 'eager') return false
return (!supportLoading && props.loading === 'lazy') || props.lazy
})
const loadImage = () => {
if (!isClient) return
// reset status
loading.value = true
isLoading.value = true
hasLoadError.value = false
const img = new Image()
const currentImageSrc = props.src
// load & error callbacks are only responsible for currentImageSrc
img.addEventListener('load', (e) => {
if (currentImageSrc !== props.src) {
return
}
handleLoad(e, img)
})
img.addEventListener('error', (e) => {
if (currentImageSrc !== props.src) {
return
}
handleError(e)
})
// bind html attrs
// so it can behave consistently
Object.entries(rawAttrs).forEach(([key, value]) => {
// avoid onload to be overwritten
if (key.toLowerCase() === 'onload') return
img.setAttribute(key, value as string)
})
img.src = currentImageSrc
imageSrc.value = props.src
}
function handleLoad(e: Event, img: HTMLImageElement) {
imgWidth.value = img.width
imgHeight.value = img.height
loading.value = false
function handleLoad() {
isLoading.value = false
hasLoadError.value = false
}
function handleError(event: Event) {
loading.value = false
isLoading.value = false
hasLoadError.value = true
emit('error', event)
}
Expand Down Expand Up @@ -235,9 +216,9 @@ function switchViewer(val: number) {
watch(
() => props.src,
() => {
if (props.lazy) {
if (isManual.value) {
// reset status
loading.value = true
isLoading.value = true
hasLoadError.value = false
removeLazyLoadListener()
addLazyLoadListener()
Expand All @@ -248,7 +229,7 @@ watch(
)
onMounted(() => {
if (props.lazy) {
if (isManual.value) {
addLazyLoadListener()
} else {
loadImage()
Expand Down
8 changes: 8 additions & 0 deletions packages/theme-chalk/src/image.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
height: 100%;
}

%position {
position: absolute;
top: 0;
left: 0;
}

@include b(image) {
position: relative;
display: inline-block;
Expand All @@ -17,11 +23,13 @@
}

@include e(placeholder) {
@extend %position !optional;
@extend %size !optional;
background: getCssVar('fill-color', 'light');
}

@include e(error) {
@extend %position !optional;
@extend %size !optional;
display: flex;
justify-content: center;
Expand Down

0 comments on commit 60cd22b

Please sign in to comment.