diff --git a/components.json b/components.json index f595fd87a..03f893b4d 100644 --- a/components.json +++ b/components.json @@ -70,5 +70,6 @@ "footer": "./packages/footer/index.js", "timeline": "./packages/timeline/index.js", "timeline-item": "./packages/timeline-item/index.js", - "divider": "./packages/divider/index.js" + "divider": "./packages/divider/index.js", + "image": "./packages/image/index.js" } diff --git a/examples/demo-styles/image.scss b/examples/demo-styles/image.scss new file mode 100644 index 000000000..9bf49967d --- /dev/null +++ b/examples/demo-styles/image.scss @@ -0,0 +1,78 @@ +@keyframes dot { + 0% { width: 0; margin-right: 1em; } + 100% { width: 1em; margin-right: 0; } +} + +.demo-image { + .block { + padding: 30px 0; + text-align: center; + border-right: solid 1px #eff2f6; + display: inline-block; + width: 20%; + box-sizing: border-box; + vertical-align: top; + &:last-child { + border-right: none; + } + } + + .demonstration { + display: block; + color: #8492a6; + font-size: 14px; + margin-bottom: 20px; + } +} + +.demo-image__placeholder, .demo-image__error { + @extend .demo-image; + + .block { + width: 49%; + } + + .el-image { + width: 300px; + height: 200px; + } + + .image-slot { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + background: #f5f7fa; + color: #909399; + font-size: 14px; + } +} + +.demo-image__placeholder { + .dot { + animation: dot 2s infinite steps(3, start); + overflow: hidden; + } +} + +.demo-image__error { + .image-slot { + font-size: 30px; + } +} + +.demo-image__lazy { + height: 400px; + overflow-y: auto; + + .el-image { + display: block; + min-height: 200px; + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } +} diff --git a/examples/demo-styles/index.scss b/examples/demo-styles/index.scss index 26e3ad7cd..026f06251 100644 --- a/examples/demo-styles/index.scss +++ b/examples/demo-styles/index.scss @@ -39,3 +39,4 @@ @import "./typography.scss"; @import "./upload.scss"; @import "./divider.scss"; +@import "./image.scss"; diff --git a/examples/docs/en-US/image.md b/examples/docs/en-US/image.md new file mode 100644 index 000000000..0e621d0bd --- /dev/null +++ b/examples/docs/en-US/image.md @@ -0,0 +1,132 @@ +## Image +Besides the native features of img, support lazy load, custom placeholder and load failure, etc. + +### Basic Usage + +:::demo Indicate how the image should be resized to fit its container by `fit`, same as native [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)。 +```html +
+
+ {{ fit }} + +
+
+ + +``` +::: + +### Placeholder + +:::demo Custom placeholder content when image hasn't loaded yet by `slot = placeholder` +```html +
+
+ Default + +
+
+ Custom + +
+ Loading... +
+
+
+
+ + +``` +::: + +### Load Failed + +:::demo Custom failed content when error occurs to image load by `slot = error` +```html +
+
+ Default + +
+
+ Custom + +
+ +
+
+
+
+``` +::: + +### Lazy Load + +:::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. +```html +
+ +
+ + +``` +::: + +### Attributes +| Attribute | Description | Type | Accepted values | 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 | — | - | +| alt | Native alt | string | - | - | +| 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 | + +### Events +| Event Name | Description | Parameters | +|---------- |-------- |---------- | +| load | Same as native load | (e: Event) | +| error | Same as native error | (e: Error) | + +### Slots +| Slot Name | Description | +|---------|-------------| +| placeholder | Triggers when image load | +| error | Triggers when image load failed | + + diff --git a/examples/docs/es/image.md b/examples/docs/es/image.md new file mode 100644 index 000000000..0e621d0bd --- /dev/null +++ b/examples/docs/es/image.md @@ -0,0 +1,132 @@ +## Image +Besides the native features of img, support lazy load, custom placeholder and load failure, etc. + +### Basic Usage + +:::demo Indicate how the image should be resized to fit its container by `fit`, same as native [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)。 +```html +
+
+ {{ fit }} + +
+
+ + +``` +::: + +### Placeholder + +:::demo Custom placeholder content when image hasn't loaded yet by `slot = placeholder` +```html +
+
+ Default + +
+
+ Custom + +
+ Loading... +
+
+
+
+ + +``` +::: + +### Load Failed + +:::demo Custom failed content when error occurs to image load by `slot = error` +```html +
+
+ Default + +
+
+ Custom + +
+ +
+
+
+
+``` +::: + +### Lazy Load + +:::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. +```html +
+ +
+ + +``` +::: + +### Attributes +| Attribute | Description | Type | Accepted values | 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 | — | - | +| alt | Native alt | string | - | - | +| 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 | + +### Events +| Event Name | Description | Parameters | +|---------- |-------- |---------- | +| load | Same as native load | (e: Event) | +| error | Same as native error | (e: Error) | + +### Slots +| Slot Name | Description | +|---------|-------------| +| placeholder | Triggers when image load | +| error | Triggers when image load failed | + + diff --git a/examples/docs/fr-FR/image.md b/examples/docs/fr-FR/image.md new file mode 100644 index 000000000..0e621d0bd --- /dev/null +++ b/examples/docs/fr-FR/image.md @@ -0,0 +1,132 @@ +## Image +Besides the native features of img, support lazy load, custom placeholder and load failure, etc. + +### Basic Usage + +:::demo Indicate how the image should be resized to fit its container by `fit`, same as native [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)。 +```html +
+
+ {{ fit }} + +
+
+ + +``` +::: + +### Placeholder + +:::demo Custom placeholder content when image hasn't loaded yet by `slot = placeholder` +```html +
+
+ Default + +
+
+ Custom + +
+ Loading... +
+
+
+
+ + +``` +::: + +### Load Failed + +:::demo Custom failed content when error occurs to image load by `slot = error` +```html +
+
+ Default + +
+
+ Custom + +
+ +
+
+
+
+``` +::: + +### Lazy Load + +:::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. +```html +
+ +
+ + +``` +::: + +### Attributes +| Attribute | Description | Type | Accepted values | 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 | — | - | +| alt | Native alt | string | - | - | +| 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 | + +### Events +| Event Name | Description | Parameters | +|---------- |-------- |---------- | +| load | Same as native load | (e: Event) | +| error | Same as native error | (e: Error) | + +### Slots +| Slot Name | Description | +|---------|-------------| +| placeholder | Triggers when image load | +| error | Triggers when image load failed | + + diff --git a/examples/docs/zh-CN/image.md b/examples/docs/zh-CN/image.md new file mode 100644 index 000000000..055df56bd --- /dev/null +++ b/examples/docs/zh-CN/image.md @@ -0,0 +1,132 @@ +## Image 图片 +图片容器,在保留原生img的特性下,支持懒加载,自定义占位、加载失败等 + +### 基础用法 + +:::demo 可通过`fit`确定图片如何适应到容器框,同原生 [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)。 +```html +
+
+ {{ fit }} + +
+
+ + +``` +::: + +### 占位内容 + +:::demo 可通过`slot = placeholder`可自定义占位内容 +```html +
+
+ 默认 + +
+
+ 自定义 + +
+ 加载中... +
+
+
+
+ + +``` +::: + +### 加载失败 + +:::demo 可通过`slot = error`可自定义加载失败内容 +```html +
+
+ 默认 + +
+
+ 自定义 + +
+ +
+
+
+
+``` +::: + +### 懒加载 + +:::demo 可通过`lazy`开启懒加载功能,当图片滚动到可视范围内才会加载。可通过`scroll-container`来设置滚动容器,若未定义,则为最近一个`overflow`值为`auto`或`scroll`的父元素。 +```html +
+ +
+ + +``` +::: + +### Attributes +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +|---------- |-------- |---------- |------------- |-------- | +| src | 图片源,同原生 | string | — | - | +| fit | 确定图片如何适应容器框,同原生 [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) | fill / contain / cover / none / scale-down | — | - | +| alt | 原生 alt | string | - | - | +| lazy | 是否开启懒加载 | boolean | — | false | +| scroll-container | 开启懒加载后,监听 scroll 事件的容器 | string / HTMLElement | — | 最近一个 overflow 值为 auto 或 scroll 的父元素 | + +### Events +| 事件名称 | 说明 | 回调参数 | +|---------- |-------- |---------- | +| load | 图片加载成功触发 | (e: Event) | +| error | 图片加载失败触发 | (e: Error) | + +### Slots +| 名称 | 说明 | +|---------|-------------| +| placeholder | 图片未加载的占位内容 | +| error | 加载失败的内容 | + + diff --git a/examples/nav.config.json b/examples/nav.config.json index 40a3e3de9..0ae34df88 100644 --- a/examples/nav.config.json +++ b/examples/nav.config.json @@ -255,6 +255,10 @@ { "path": "/divider", "title": "Divider 分割线" + }, + { + "path": "/image", + "title": "Image" } ] } @@ -517,6 +521,10 @@ { "path": "/divider", "title": "Divider" + }, + { + "path": "/image", + "title": "Image" } ] } @@ -779,6 +787,10 @@ { "path": "/divider", "title": "Divider" + }, + { + "path": "/image", + "title": "Image" } ] } @@ -1041,6 +1053,10 @@ { "path": "/divider", "title": "Divider" + }, + { + "path": "/image", + "title": "Image 图片" } ] } diff --git a/packages/image/index.js b/packages/image/index.js new file mode 100644 index 000000000..5e1d9656e --- /dev/null +++ b/packages/image/index.js @@ -0,0 +1,8 @@ +import Image from './src/main'; + +/* istanbul ignore next */ +Image.install = function(Vue) { + Vue.component(Image.name, Image); +}; + +export default Image; diff --git a/packages/image/src/main.vue b/packages/image/src/main.vue new file mode 100644 index 000000000..b66a6e6dc --- /dev/null +++ b/packages/image/src/main.vue @@ -0,0 +1,123 @@ + + + diff --git a/packages/theme-chalk/src/image.scss b/packages/theme-chalk/src/image.scss new file mode 100644 index 000000000..528707bb5 --- /dev/null +++ b/packages/theme-chalk/src/image.scss @@ -0,0 +1,32 @@ +@import "mixins/mixins"; +@import "common/var"; + +%size { + width: 100%; + height: 100%; +} + +@include b(image) { + display: inline-block; + + @include e(inner) { + @extend %size; + vertical-align: top; + } + + @include e(placeholder) { + @extend %size; + background: $--background-color-base; + } + + @include e(error) { + @extend %size; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + background: $--background-color-base; + color: $--color-text-placeholder; + vertical-align: middle; + } +} diff --git a/packages/theme-chalk/src/index.scss b/packages/theme-chalk/src/index.scss index 4051e6434..0cd602b59 100644 --- a/packages/theme-chalk/src/index.scss +++ b/packages/theme-chalk/src/index.scss @@ -68,3 +68,4 @@ @import "./timeline.scss"; @import "./timeline-item.scss"; @import "./divider.scss"; +@import "./image.scss"; diff --git a/src/index.js b/src/index.js index ef603ee86..a388f369c 100644 --- a/src/index.js +++ b/src/index.js @@ -72,6 +72,7 @@ import Footer from '../packages/footer/index.js'; import Timeline from '../packages/timeline/index.js'; import TimelineItem from '../packages/timeline-item/index.js'; import Divider from '../packages/divider/index.js'; +import Image from '../packages/image/index.js'; import locale from 'element-ui/src/locale'; import CollapseTransition from 'element-ui/src/transitions/collapse-transition'; @@ -144,6 +145,7 @@ const components = [ Timeline, TimelineItem, Divider, + Image, CollapseTransition ]; @@ -254,5 +256,6 @@ export default { Footer, Timeline, TimelineItem, - Divider + Divider, + Image }; diff --git a/src/locale/lang/af-ZA.js b/src/locale/lang/af-ZA.js index 5f09d42e7..cbb513295 100644 --- a/src/locale/lang/af-ZA.js +++ b/src/locale/lang/af-ZA.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Voer sleutelwoord in', noCheckedFormat: '{total} items', hasCheckedFormat: '{checked}/{total} gekies' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ar.js b/src/locale/lang/ar.js index 251f68d4e..da074098b 100644 --- a/src/locale/lang/ar.js +++ b/src/locale/lang/ar.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'ادخل كلمة', noCheckedFormat: '{total} عناصر', hasCheckedFormat: '{checked}/{total} مختار' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/bg.js b/src/locale/lang/bg.js index a8ab93131..1dd8468bc 100644 --- a/src/locale/lang/bg.js +++ b/src/locale/lang/bg.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ca.js b/src/locale/lang/ca.js index 854432f45..6a4aa2b17 100644 --- a/src/locale/lang/ca.js +++ b/src/locale/lang/ca.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Introdueix la paraula clau', noCheckedFormat: '{total} ítems', hasCheckedFormat: '{checked}/{total} seleccionats' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/cs-CZ.js b/src/locale/lang/cs-CZ.js index 6529ac619..24c74e9a6 100644 --- a/src/locale/lang/cs-CZ.js +++ b/src/locale/lang/cs-CZ.js @@ -106,6 +106,9 @@ export default { filterPlaceholder: 'Klíčové slovo', noCheckedFormat: '{total} položek', hasCheckedFormat: '{checked}/{total} vybráno' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/da.js b/src/locale/lang/da.js index 3501e62ac..b8c867090 100644 --- a/src/locale/lang/da.js +++ b/src/locale/lang/da.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Indtast søgeord', noCheckedFormat: '{total} emner', hasCheckedFormat: '{checked}/{total} valgt' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/de.js b/src/locale/lang/de.js index 84877c494..17c7913fe 100644 --- a/src/locale/lang/de.js +++ b/src/locale/lang/de.js @@ -105,6 +105,9 @@ export default { filterPlaceholder: 'Einträge filtern', noCheckedFormat: '{total} Einträge', hasCheckedFormat: '{checked}/{total} ausgewählt' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ee.js b/src/locale/lang/ee.js index 1f7f1af77..5c097e2ad 100644 --- a/src/locale/lang/ee.js +++ b/src/locale/lang/ee.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Sisesta märksõna', noCheckedFormat: '{total} objekti', hasCheckedFormat: '{checked}/{total} valitud' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/el.js b/src/locale/lang/el.js index 4eae67fae..52688e072 100644 --- a/src/locale/lang/el.js +++ b/src/locale/lang/el.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Αναζήτηση', noCheckedFormat: '{total} Αντικείμενα', hasCheckedFormat: '{checked}/{total} επιλεγμένα' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/en.js b/src/locale/lang/en.js index 808bbf232..94d9c662f 100644 --- a/src/locale/lang/en.js +++ b/src/locale/lang/en.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' } } }; diff --git a/src/locale/lang/es.js b/src/locale/lang/es.js index 6067f427e..e20824499 100644 --- a/src/locale/lang/es.js +++ b/src/locale/lang/es.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Ingresar palabra clave', noCheckedFormat: '{total} artículos', hasCheckedFormat: '{checked}/{total} revisados' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/eu.js b/src/locale/lang/eu.js index 8caa109f1..dfa5d9301 100644 --- a/src/locale/lang/eu.js +++ b/src/locale/lang/eu.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Sartu gako-hitza', // to be translated noCheckedFormat: '{total} elementu', // to be translated hasCheckedFormat: '{checked}/{total} hautatuta' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/fa.js b/src/locale/lang/fa.js index 8ffc0e57b..b1255179d 100644 --- a/src/locale/lang/fa.js +++ b/src/locale/lang/fa.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'کلید واژه هارو وارد کن', noCheckedFormat: '{total} مورد', hasCheckedFormat: '{checked} مورد از {total} مورد انتخاب شده است' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/fi.js b/src/locale/lang/fi.js index 544455077..0ae38e482 100644 --- a/src/locale/lang/fi.js +++ b/src/locale/lang/fi.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Syötä hakusana', noCheckedFormat: '{total} kohdetta', hasCheckedFormat: '{checked}/{total} valittu' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/fr.js b/src/locale/lang/fr.js index 41ac3f3b6..98b9a3429 100644 --- a/src/locale/lang/fr.js +++ b/src/locale/lang/fr.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Entrer un mot clef', noCheckedFormat: '{total} elements', hasCheckedFormat: '{checked}/{total} coché(s)' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/he.js b/src/locale/lang/he.js index 7a5f8ffc4..2cb92ca0a 100644 --- a/src/locale/lang/he.js +++ b/src/locale/lang/he.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'הקלד', noCheckedFormat: 'פריטים {total}', hasCheckedFormat: ' אישור {checked}/{total}' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/hr.js b/src/locale/lang/hr.js index afd348a5a..ec341834b 100644 --- a/src/locale/lang/hr.js +++ b/src/locale/lang/hr.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Unesite ključnu riječ', // to be translated noCheckedFormat: '{total} stavki', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/hu.js b/src/locale/lang/hu.js index d1840555a..89b87f46d 100644 --- a/src/locale/lang/hu.js +++ b/src/locale/lang/hu.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Kulcsszó', noCheckedFormat: '{total} elem', hasCheckedFormat: '{checked}/{total} kiválasztva' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/hy-AM.js b/src/locale/lang/hy-AM.js index 306a464ca..7b60d040a 100644 --- a/src/locale/lang/hy-AM.js +++ b/src/locale/lang/hy-AM.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Մուտքագրեք բանալի բառ', noCheckedFormat: '{total} միաւոր', hasCheckedFormat: '{checked}/{total} ընտրուած է' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/id.js b/src/locale/lang/id.js index 9194b7866..2163b1c98 100644 --- a/src/locale/lang/id.js +++ b/src/locale/lang/id.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Masukan kata kunci', noCheckedFormat: '{total} butir', hasCheckedFormat: '{checked}/{total} terpilih' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/it.js b/src/locale/lang/it.js index 6fa262ad2..b4006421d 100644 --- a/src/locale/lang/it.js +++ b/src/locale/lang/it.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Inserisci filtro', noCheckedFormat: '{total} elementi', hasCheckedFormat: '{checked}/{total} selezionati' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ja.js b/src/locale/lang/ja.js index ff08f1f1c..fa9e0d103 100644 --- a/src/locale/lang/ja.js +++ b/src/locale/lang/ja.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'キーワードを入力', noCheckedFormat: '総計 {total} 件', hasCheckedFormat: '{checked}/{total} を選択した' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/kg.js b/src/locale/lang/kg.js index 03420776e..470a8f4a6 100644 --- a/src/locale/lang/kg.js +++ b/src/locale/lang/kg.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Сураныч, издөө кирет', noCheckedFormat: 'бүтүндөй {total} сан', hasCheckedFormat: 'Тандалган {checked}/{total} сан' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/km.js b/src/locale/lang/km.js index 97d2fc371..eb8251bd4 100644 --- a/src/locale/lang/km.js +++ b/src/locale/lang/km.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'បញ្ចូលពាក្យ', noCheckedFormat: '{total} ធាតុ', hasCheckedFormat: '{checked}/{total} បានគូសធីក' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ko.js b/src/locale/lang/ko.js index d203366c4..aafd3d63a 100644 --- a/src/locale/lang/ko.js +++ b/src/locale/lang/ko.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: ' 입력하세요', noCheckedFormat: '{total} 항목', hasCheckedFormat: '{checked}/{total} 선택됨' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ku.js b/src/locale/lang/ku.js index 88328ffcb..97a150c1a 100644 --- a/src/locale/lang/ku.js +++ b/src/locale/lang/ku.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Binivîse', noCheckedFormat: '{total} lib', hasCheckedFormat: '{checked}/{total} bijartin' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/kz.js b/src/locale/lang/kz.js index 01c43823e..81e2ee3d2 100644 --- a/src/locale/lang/kz.js +++ b/src/locale/lang/kz.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Кілт сөзді енгізіңіз', noCheckedFormat: '{total} элэмэнт', hasCheckedFormat: '{checked}/{total} құсбелгісі қойылды' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/lt.js b/src/locale/lang/lt.js index f8cb51754..156e248f1 100644 --- a/src/locale/lang/lt.js +++ b/src/locale/lang/lt.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Įvesk raktažodį', noCheckedFormat: 'Viso: {total}', hasCheckedFormat: 'Pažymėta {checked} iš {total}' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/lv.js b/src/locale/lang/lv.js index ae4554251..f8afe29db 100644 --- a/src/locale/lang/lv.js +++ b/src/locale/lang/lv.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Ievadīt atslēgvārdu', noCheckedFormat: '{total} vienības', hasCheckedFormat: '{checked}/{total} atzīmēti' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/mn.js b/src/locale/lang/mn.js index df563b599..e85019af3 100644 --- a/src/locale/lang/mn.js +++ b/src/locale/lang/mn.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Утга оруул', noCheckedFormat: '{total} өгөгдөл', hasCheckedFormat: '{checked}/{total} сонгосон' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/nb-NO.js b/src/locale/lang/nb-NO.js index 91c8ffe78..e8692b410 100644 --- a/src/locale/lang/nb-NO.js +++ b/src/locale/lang/nb-NO.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/nl.js b/src/locale/lang/nl.js index 6c76f20ce..1715e9223 100644 --- a/src/locale/lang/nl.js +++ b/src/locale/lang/nl.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Geef zoekwoerd', noCheckedFormat: '{total} items', hasCheckedFormat: '{checked}/{total} geselecteerd' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/pl.js b/src/locale/lang/pl.js index 8cc5c06f3..37b02b381 100644 --- a/src/locale/lang/pl.js +++ b/src/locale/lang/pl.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Wpisz szukaną frazę', noCheckedFormat: 'razem: {total}', hasCheckedFormat: 'wybranych: {checked}/{total}' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/pt-br.js b/src/locale/lang/pt-br.js index 5b0009a40..6f6f37748 100644 --- a/src/locale/lang/pt-br.js +++ b/src/locale/lang/pt-br.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Digite uma palavra-chave', noCheckedFormat: '{total} itens', hasCheckedFormat: '{checked}/{total} selecionados' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/pt.js b/src/locale/lang/pt.js index e8aacad3c..89a4e4f37 100644 --- a/src/locale/lang/pt.js +++ b/src/locale/lang/pt.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ro.js b/src/locale/lang/ro.js index 16a7ad15b..8fb218c35 100644 --- a/src/locale/lang/ro.js +++ b/src/locale/lang/ro.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Introduceți cuvântul cheie', noCheckedFormat: '{total} elemente', hasCheckedFormat: '{checked}/{total} verificate' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ru-RU.js b/src/locale/lang/ru-RU.js index 890f111bd..c6a936c3f 100644 --- a/src/locale/lang/ru-RU.js +++ b/src/locale/lang/ru-RU.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Введите ключевое слово', noCheckedFormat: '{total} пунктов', hasCheckedFormat: '{checked}/{total} выбрано' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/sk.js b/src/locale/lang/sk.js index 0ef62c4e3..8aa383260 100644 --- a/src/locale/lang/sk.js +++ b/src/locale/lang/sk.js @@ -106,6 +106,9 @@ export default { filterPlaceholder: 'Filtrovať podľa', noCheckedFormat: '{total} položiek', hasCheckedFormat: '{checked}/{total} označených' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/sl.js b/src/locale/lang/sl.js index f6000a071..b6cd253d3 100644 --- a/src/locale/lang/sl.js +++ b/src/locale/lang/sl.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Vnesi ključno besedo', noCheckedFormat: '{total} elementov', hasCheckedFormat: '{checked}/{total} izbranih' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/sr.js b/src/locale/lang/sr.js index fe2f108e7..7d3e7b048 100644 --- a/src/locale/lang/sr.js +++ b/src/locale/lang/sr.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Унеси кључну реч', // to be translated noCheckedFormat: '{total} ставки', // to be translated hasCheckedFormat: '{checked}/{total} обележених' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/sv-SE.js b/src/locale/lang/sv-SE.js index 0be73a0b2..aed00aea3 100644 --- a/src/locale/lang/sv-SE.js +++ b/src/locale/lang/sv-SE.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ta.js b/src/locale/lang/ta.js index 7b458112f..694501adf 100644 --- a/src/locale/lang/ta.js +++ b/src/locale/lang/ta.js @@ -103,6 +103,9 @@ export default { filterPlaceholder: 'சொல்லை உள்ளீடு செய்', noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} தேர்வு செய்யப்பட்டவைகள்' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/th.js b/src/locale/lang/th.js index e45a392e7..0475f51e2 100644 --- a/src/locale/lang/th.js +++ b/src/locale/lang/th.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/tk.js b/src/locale/lang/tk.js index 6db1e0cba..6b8bb6282 100644 --- a/src/locale/lang/tk.js +++ b/src/locale/lang/tk.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Gözleg sözlerini giriziň', noCheckedFormat: '{total} sany', hasCheckedFormat: '{checked}/{total} saýlanan' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/tr-TR.js b/src/locale/lang/tr-TR.js index de5e68fb2..6e3cb629e 100644 --- a/src/locale/lang/tr-TR.js +++ b/src/locale/lang/tr-TR.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Anahtar kelimeleri gir', noCheckedFormat: '{total} adet', hasCheckedFormat: '{checked}/{total} seçildi' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ua.js b/src/locale/lang/ua.js index 45118931b..56022259c 100644 --- a/src/locale/lang/ua.js +++ b/src/locale/lang/ua.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Введіть ключове слово', noCheckedFormat: '{total} пунктів', hasCheckedFormat: '{checked}/{total} вибрано' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/ug-CN.js b/src/locale/lang/ug-CN.js index 6db539251..0c94b08fd 100644 --- a/src/locale/lang/ug-CN.js +++ b/src/locale/lang/ug-CN.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'ئىزدىمەكچى بولغان مەزمۇننى كىرگۈزۈڭ', noCheckedFormat: 'جەمئىي {total} تۈر', hasCheckedFormat: 'تاللانغىنى {checked}/{total} تۈر' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/vi.js b/src/locale/lang/vi.js index a09d68e09..24fd444f4 100644 --- a/src/locale/lang/vi.js +++ b/src/locale/lang/vi.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Nhập từ khóa', noCheckedFormat: '{total} mục', hasCheckedFormat: '{checked}/{total} đã chọn ' + }, + image: { + error: 'FAILED' // to be translated } } }; diff --git a/src/locale/lang/zh-CN.js b/src/locale/lang/zh-CN.js index 6d25fd240..fa4b74799 100644 --- a/src/locale/lang/zh-CN.js +++ b/src/locale/lang/zh-CN.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: '请输入搜索内容', noCheckedFormat: '共 {total} 项', hasCheckedFormat: '已选 {checked}/{total} 项' + }, + image: { + error: '加载失败' } } }; diff --git a/src/locale/lang/zh-TW.js b/src/locale/lang/zh-TW.js index 03b257289..9b87d2649 100644 --- a/src/locale/lang/zh-TW.js +++ b/src/locale/lang/zh-TW.js @@ -104,6 +104,9 @@ export default { filterPlaceholder: 'Enter keyword', // to be translated noCheckedFormat: '{total} items', // to be translated hasCheckedFormat: '{checked}/{total} checked' // to be translated + }, + image: { + error: '加載失敗' } } }; diff --git a/src/utils/dom.js b/src/utils/dom.js index 0221295f5..8d2c66807 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -172,3 +172,56 @@ export function setStyle(element, styleName, value) { } } }; + +export const isScroll = (el, vertical) => { + if (isServer) return; + + const determinedDirection = vertical !== null || vertical !== undefined; + const overflow = determinedDirection + ? vertical + ? getStyle(el, 'overflow-y') + : getStyle(el, 'overflow-x') + : getStyle(el, 'overflow'); + + return overflow.match(/(scroll|auto)/); +}; + +export const getScrollContainer = (el, vertical) => { + if (isServer) return; + + let parent = el; + while (parent) { + if ([window, document, document.documentElement].includes(parent)) { + return window; + } + if (isScroll(parent, vertical)) { + return parent; + } + parent = parent.parentNode; + } + + return parent; +}; + +export const isInContainer = (el, container) => { + if (isServer || !el || !container) return false; + + const elRect = el.getBoundingClientRect(); + let containerRect; + + if ([window, document, document.documentElement, null, undefined].includes(container)) { + containerRect = { + top: 0, + right: window.innerWidth, + bottom: window.innerHeight, + left: 0 + }; + } else { + containerRect = container.getBoundingClientRect(); + } + + return elRect.top < containerRect.bottom && + elRect.bottom > containerRect.top && + elRect.right > containerRect.left && + elRect.left < containerRect.right; +}; diff --git a/src/utils/types.js b/src/utils/types.js index 828773341..a5d449231 100644 --- a/src/utils/types.js +++ b/src/utils/types.js @@ -1,3 +1,11 @@ +export function isString(obj) { + return Object.prototype.toString.call(obj) === '[object String]'; +} + export function isObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]'; } + +export function isHtmlElement(node) { + return node && node.nodeType === Node.ELEMENT_NODE; +} diff --git a/test/unit/specs/image.spec.js b/test/unit/specs/image.spec.js new file mode 100644 index 000000000..6dca7ec59 --- /dev/null +++ b/test/unit/specs/image.spec.js @@ -0,0 +1,68 @@ +import { createTest, createVue, destroyVM, wait } from '../util'; +import Image from 'packages/image'; + +const src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAFmklEQVR4Xu2ba2wUVRTH/2cWtlaooFQwKpr4CkESTQBNUAkodGeaKFGD2u6WGPyAmmAEEZmpj1VhBghE/KKGBIztDIXAByWBnQ1oQREfARNiDDHiF1+ooBiksVt295hFiqSdxx3KdmcH5uv+z+3//O65c19TwgX+0AWePy4CuFgBISCQ3J6+DFLuVQKmg2k4gK3xuvwr6+9e+Xe57YViCCRttY1ALX2S/asIPLRB1jvLCSEUAFK2egKgYf0T5a6TxdhtmxqXfl8uCBUHMHfP4rqeE0OOeyS4w5T1hsgCmLNTvaWYp299EpxoyvpX5YBQ8QpotlvvkMBfeCbHvMpUjOejCSCjPSYROrySY/B+SzYmRRJA0tZWEfCcJwBGz81yvDZN6eL5hlDxIZCyta8BTPBLjIv5sVbjyp/8dEF/ryiAlL3kJkD6TsQ0STS5vWHZPhFtEE1FAYiUf28yBaZ7OpRle4IkJ6KtGIDZexfU1hyv/QXASBGjBeYpHYrxmYg2iKZiAFIZdSGIVouaLVJx0obE8v2ielFdRQA83pkemc/lDgE0StRopF6CSVuzCGgWTb6ku7T+aHztpLUng8SIaAe9ApJ265MEflvE3P8a/t2UjTHBYsTUgwqgKauOk4p0gAhxMXunVcwfmYpxX6AYQfGgAWjqTNdL3bndRDRe0NsZGYPXWLKxIGiciH5QAJye8r4UWfE5mWbCE1ZCXy+SUFBN2QEo2+fXjKK6zSDcH9Rcr55i0vj2mUsPnmu8V1xZAZxKXhq+G6A7z9U8Mx+zFOOKc433iysbgNKYj3XnNoHoXj8T3r/z+6ZsPDiwNtyjHQE07dTGSAU8QkX4LlSY6Ndh9UfWnT1Ht2QXjeZifC8INw7UOAMfE2MgB6NdhaFo65ih/+bkxRFAMqPuIaK7xM3zu6ZszO3Vt2S1CcwobXPD8TAfoZjU6LSb7AdgWmd6yLW5nqArrm5T1mtDC+A/Y3tNWe/XqY4VkLK1fwBcItp9DGZLNqQwA2DmgiUbQ0Hgs/NyBpBRD4PoqigBKOWSGxuv2XxruscXQNJWDxJoXJQAMPCnJev9XuouQ0D9PMjcXQ1DAIx9pqJP7tupLrOAZhMhEaUKAGOjqehNYgBsdSOBHo0UAOBlU9ZfFwOQUd8honmRAsA8y1SMrWIAbHUFgRZHCUCe6LqNiWU/igHIaCoR9KgAYMZRS9GvFF4Kp7LqU2B6KzoAeKulGLOEASRtrZkAK0IAVEsxlgsDaM6ojRLRtqgAIImntTcYu4UBpLKtU8D8aRQAMJD/uSZeu2t6Oi8MILldHU8SfRMFAG67wN7cHFeCcz5svaZ4koWvosO9FGbdlI1Wt850BNCSXTSMOX4iEhVAaDAT+o5AAEriVEYrgHBmj+8FI7QVwJzLjei+fPOUN0rnG46P66FoMqMdIUK9SBWEGIDvjZIrgFRGOyR6qBlWAMzQLEU3vDrRvQJsdR+BJlZzBYh8VuM1BHYSQehCMpwVwF1mwqjrewYotBk69RK0tS0AHq7eChC7UPEYAto6As6c9VfdLED8tJkwfL9DcH8J2upqgBZWbwXgalPWD/v59wCgvQTgNb8GSr+H7x3AB0zZuF3EuyuA5oz6jET0pkgjYQPAzEstxSh1oO/jVQFzALzn20IIK0CKFa5vm7niBxHvHgsh9QEQfSDSSP8KWDSaOe54GyvS3sA0vMuUjemibbgPgWzrVInZ8RChb+N9AQSdRkXNiul4qikbn4hp4f5/g7N3vDAiXpCOEcj/IwpG0VT02Nl/9NSOshjvGMinMaJJ9OqYeYulGLODxHkml7TVZ8GYT0Q3uDfKXQxaY8n6i06aZnvJDGKaB8JMAo0IYk5UW7r5BbAtNhRz22YYf4jGlXT+vRuktSrUXgRQhZ12Xi1f8BXwL38cy1+mrtJNAAAAAElFTkSuQmCC'; + +describe('Image', () => { + let vm; + + afterEach(() => { + destroyVM(vm); + }); + + it('create', async() => { + vm = createTest(Image, { + src, + fit: 'fill' + }, true); + const placeholder = vm.$el.querySelector('.el-image__placeholder'); + const error = vm.$el.querySelector('.el-image__error'); + let img = vm.$el.querySelector('.el-image__inner'); + expect(placeholder).to.exist; + expect(error).to.be.null; + expect(img).to.be.null; + + await wait(); + img = vm.$el.querySelector('.el-image__inner'); + expect(img.style.objectFit).to.equal('fill'); + }); + + it('load failed', async() => { + vm = createTest(Image, true); + await wait(); + const error = vm.$el.querySelector('.el-image__error'); + expect(error).to.be.exist; + }); + + it('lazy load', async() => { + vm = createVue({ + template: ` +
+ +
+ `, + data() { + return { + src + }; + } + }, true); + const { image, wrapper } = vm.$refs; + const [image1, image2] = image; + + await wait(); + expect(image1.loading).to.be.false; + expect(image2.loading).to.be.true; + wrapper.scrollTop = 10; + + await wait(); + expect(image2.loading).to.be.false; + }); +}); + diff --git a/types/image.d.ts b/types/image.d.ts new file mode 100644 index 000000000..6cfa648e5 --- /dev/null +++ b/types/image.d.ts @@ -0,0 +1,34 @@ +import { VNode } from 'vue' +import { ElementUIComponent } from './component' + +export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' + +export interface ImageSlots { + /** Placeholder content when image hasn't loaded yet */ + placeholder: VNode[] + + /** Error content when error occurs to image load */ + error: VNode[] + + [key: string]: VNode[] +} + +/** Image Component */ +export declare class ElImage extends ElementUIComponent { + /** Image source */ + src: string + + /** Indicate how the image should be resized to fit its container, same as native 'object-fit' */ + fit: ObjectFit + + /** Whether to use lazy load */ + lazy: boolean + + /** Scroll container that to add scroll listener when using lazy load */ + scrollContainer: string | HTMLElement + + /** Native 'alt' attribute */ + alt: string + + $slots: ImageSlots +}