feat: new compontent Skeleton (#953)

This commit is contained in:
porcelainHeart
2020-12-21 20:07:48 +08:00
committed by GitHub
parent c8313682bf
commit 8b2febe2f6
25 changed files with 2113 additions and 2 deletions

View File

@@ -84,6 +84,8 @@ import ElTree from '@element-plus/tree'
import ElUpload from '@element-plus/upload'
import ElVirtualList from '@element-plus/virtual-list'
import ElSpace from '@element-plus/space'
import ElSkeleton from '@element-plus/skeleton'
import ElSkeletonItem from '@element-plus/skeleton-item'
import { use } from '@element-plus/locale'
// if you encountered problems alike "Can't resolve './version'"
// please run `yarn bootstrap` first
@@ -181,6 +183,8 @@ const components = [
ElUpload,
ElVirtualList,
ElSpace,
ElSkeleton,
ElSkeletonItem,
]
const plugins = [
@@ -292,6 +296,8 @@ export {
ElUpload,
ElVirtualList,
ElSpace,
ElSkeleton,
ElSkeletonItem,
version,
install,
locale,

View File

@@ -5,3 +5,4 @@ export { default as useRestoreActive } from './use-restore-active'
export { default as useModal } from './use-modal'
export { default as useMigrating } from './use-migrating'
export { default as useFocus } from './use-focus'
export { default as useThrottleRender } from './use-throttle-render'

View File

@@ -0,0 +1,31 @@
import { onMounted, ref, watch } from 'vue'
import type { Ref } from 'vue'
export default function(
loading: Ref<boolean>,
throttle = 0,
) {
if (throttle === 0) return loading
const throttled = ref(false)
let timeoutHandle: TimeoutHandle = 0
const dispatchThrottling = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle)
}
timeoutHandle = window.setTimeout(() => {
throttled.value = loading.value
}, throttle)
}
onMounted(dispatchThrottling)
watch(() => loading.value, val => {
if (val) {
dispatchThrottling()
} else {
throttled.value = val
}
})
return throttled
}

View File

@@ -0,0 +1,24 @@
import makeMount from '@element-plus/test-utils/make-mount'
import SkeletonItem from '../src/index.vue'
describe('<skeleton-item />', () => {
const mount = makeMount(SkeletonItem, {})
test('should render correctly', () => {
const wrapper = mount()
expect(wrapper.find('.el-skeleton__text').exists()).toBe(true)
})
test('should render image placeholder', () => {
const wrapper = mount({
props: {
variant: 'image',
},
})
expect(wrapper.find('svg').exists()).toBe(true)
})
})

View File

@@ -0,0 +1,8 @@
import { App } from 'vue'
import SkeletonItem from './src/index.vue'
SkeletonItem.install = (app: App): void => {
app.component(SkeletonItem.name, SkeletonItem)
}
export default SkeletonItem

View File

@@ -0,0 +1,12 @@
{
"name": "@element-plus/skeleton-item",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.3"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.3"
}
}

View File

@@ -0,0 +1,10 @@
export type Variants =
| 'circle'
| 'rect'
| 'h1'
| 'h3'
| 'text'
| 'caption'
| 'p'
| 'image'
| 'button'

View File

@@ -0,0 +1,18 @@
<template>
<svg
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M64 896V128h896v768H64z m64-128l192-192 116.352 116.352L640 448l256 307.2V192H128v576z m224-480a96 96 0 1 1-0.064 192.064A96 96 0 0 1 352 288z"
/>
</svg>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ImgPlaceholder',
})
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div :class="['el-skeleton__item', `el-skeleton__${variant}`]">
<img-placeholder v-if="variant === 'image'" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ImgPlaceholder from './img-placeholder.vue'
import type { Variants } from './constants'
import type { PropType } from 'vue'
export default defineComponent({
name: 'ElSkeletonItem',
components: {
[ImgPlaceholder.name]: ImgPlaceholder,
},
props: {
variant: {
type: String as PropType<Variants>,
default: 'text',
},
},
})
</script>

View File

@@ -0,0 +1,91 @@
import { nextTick } from 'vue'
import makeMount from '@element-plus/test-utils/make-mount'
import Skeleton from '../src/index.vue'
const AXIOM = 'AXIOM is the best girl'
jest.useFakeTimers()
describe('Skeleton.vue', () => {
const mount = makeMount(Skeleton, { })
test('render test', () => {
const wrapper = mount()
expect(wrapper.findAll('.el-skeleton__p')).toHaveLength(4)
expect(wrapper.classes()).toContain('el-skeleton')
})
test('should render with animation', () => {
const wrapper = mount({
props: {
animated: true,
},
})
expect(wrapper.classes()).toContain('is-animated')
})
test('should render x times', async () => {
const wrapper = mount()
expect(wrapper.findAll('.el-skeleton__p')).toHaveLength(4)
await wrapper.setProps({
count: 2,
})
expect(wrapper.findAll('.el-skeleton__p')).toHaveLength(8)
})
test('should render x rows', () => {
const wrapper = mount({
props: {
rows: 4,
},
})
expect(wrapper.findAll('.el-skeleton__p')).toHaveLength(5)
})
test('should render default slots', () => {
const wrapper = mount({
slots: {
default: () => AXIOM,
},
props: {
loading: false,
},
})
expect(wrapper.text()).toBe(AXIOM)
})
test('should render templates', () => {
const wrapper = mount({
slots: {
template: () => AXIOM,
},
})
expect(wrapper.text()).toBe(AXIOM)
})
test('should throttle rendering', async () => {
const wrapper = mount({
props: {
throttle: 500,
},
})
expect((wrapper.vm as any).uiLoading).toBe(false)
jest.runAllTimers()
await nextTick()
expect((wrapper.vm as any).uiLoading).toBe(true)
})
})

View File

@@ -0,0 +1,8 @@
import { App } from 'vue'
import Skeleton from './src/index.vue'
Skeleton.install = (app: App): void => {
app.component(Skeleton.name, Skeleton)
}
export default Skeleton

View File

@@ -0,0 +1,12 @@
{
"name": "@element-plus/skeleton",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.3"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.3"
}
}

View File

@@ -0,0 +1,68 @@
<template>
<template v-if="uiLoading">
<div :class="['el-skeleton', animated ? 'is-animated' : '', ]" v-bind="$attrs">
<template v-for="i in count" :key="i">
<slot v-if="loading" name="template">
<el-skeleton-item class="is-first" variant="p" />
<el-skeleton-item
v-for="item in rows"
:key="item"
:class="{
'el-skeleton__paragraph': true,
'is-last': item === rows && rows > 1,
}"
variant="p"
/>
</slot>
</template>
</div>
</template>
<template v-else>
<slot v-bind="$attrs"></slot>
</template>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import SkeletonItem from '@element-plus/skeleton-item'
import { useThrottleRender } from '@element-plus/hooks'
export default defineComponent({
name: 'ElSkeleton',
components: {
[SkeletonItem.name]: SkeletonItem,
},
props: {
animated: {
type: Boolean,
default: false,
},
count: {
type: Number,
default: 1,
},
rows: {
type: Number,
default: 3,
},
loading: {
type: Boolean,
default: true,
},
throttle: {
type: Number,
},
},
setup(props) {
const innerLoading = computed(() => {
return props.loading
})
const uiLoading = useThrottleRender(innerLoading, props.throttle)
return {
uiLoading,
}
},
})
</script>

View File

@@ -65,6 +65,10 @@ $--border-color-lighter: #EBEEF5 !default;
/// color|1|Border Color|3
$--border-color-extra-light: #F2F6FC !default;
// Variants
$--heading-padding: 20px 0 !default;
// Background
/// color|1|Background Color|4
$--background-color-base: #F5F7FA !default;
@@ -96,6 +100,9 @@ $--box-shadow-base: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04) !de
$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .12) !default;
/// boxShadow|1|Shadow|1
$--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1) !default;
/* Svg
--------------- */
$--svg-monochrome-grey: #DCDDE0;
/* Fill
-------------------------- */
@@ -964,6 +971,9 @@ $--avatar-medium-size: 36px !default;
/// size|1|Avatar Size|3
$--avatar-small-size: 28px !default;
$--skeleton-color: #f2f2f2;
$--skeleton-to-color: #e6e6e6;
/* Break-point
--------------------------*/
$--sm: 768px !default;

View File

@@ -80,4 +80,6 @@
@import "./popconfirm.scss";
@import "./overlay.scss";
@import "./virtual-list.scss";
@import "./space.scss";
@import "./space.scss";
@import "./skeleton.scss";
@import "./skeleton-item.scss";

View File

@@ -0,0 +1,84 @@
@import 'mixins/mixins';
@import 'common/var';
@mixin circle-size($size) {
width: $size;
height: $size;
line-height: $size;
}
@include b(skeleton) {
@include e(item) {
background: $--skeleton-color;
display: inline-block;
height: 16px;
border-radius: $--border-radius-base;
width: 100%;
}
@include e(circle) {
border-radius: 50%;
@include circle-size($--avatar-medium-size);
@include m(lg) {
@include circle-size($--avatar-large-size);
}
@include m(md) {
@include circle-size($--avatar-small-size);
}
}
@include e(button) {
height: 40px;
width: 64px;
border-radius: 4px;
}
@include e(p) {
width: 100%;
@include when(last) {
width: 61%;
}
@include when(first) {
width: 33%;
}
}
@include e(text) {
width: 100%;
height: $--font-size-small;
}
@include e(caption) {
height: $--font-size-extra-small;
}
@include e(h1) {
height: $--font-size-extra-large;
}
@include e(h3) {
height: $--font-size-large;
}
@include e(h5) {
height: $--font-size-medium;
}
@include e(image) {
width: unset;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0;
svg {
fill: $--svg-monochrome-grey;
width: 22%;
height: 22%;
}
}
}

40
packages/theme-chalk/src/skeleton.scss vendored Normal file
View File

@@ -0,0 +1,40 @@
@import 'mixins/mixins';
@import 'common/var';
@import "./skeleton-item.scss";
@mixin skeleton-color() {
background: linear-gradient(
90deg,
$--skeleton-color 25%,
$--skeleton-to-color 37%,
$--skeleton-color 63%
);
background-size: 400% 100%;
animation: el-skeleton-loading 1.4s ease infinite;
}
@keyframes el-skeleton-loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
@include b(skeleton) {
width: 100%;
@each $unit in (first-line, paragraph) {
@include e($unit) {
height: 16px;
margin-top: 16px;
background: $--skeleton-color;
}
}
@include when(animated) {
.el-skeleton__item {
@include skeleton-color();
}
}
}

View File

@@ -44,4 +44,4 @@
@import "./infinite-scroll.scss";
@import "./avatar.scss";
@import "./drawer.scss";
@import "./skeleton.scss";

40
website/demo-styles/skeleton.scss vendored Normal file
View File

@@ -0,0 +1,40 @@
.demo-block.demo-skeleton {
.el-card {
margin-bottom: 16px;
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
&.multi-content {
width: 400px;
height: 267px;
}
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: '';
}
.clearfix:after {
clear: both;
}
}

View File

@@ -0,0 +1,319 @@
## Skeleton
When loading data, and you need a rich experience for visual and interactions for your end users, you can choose `skeleton`.
### Basic usage
The basic skeleton.
:::demo
```html
<template>
<el-skeleton />
</template>
```
:::
### Configurable Rows
You can configure the row numbers yourself, for more precise rendering effect, the actual rendered row number will always be 1 row more than the given number, that is because we are rendering a title row with 33% width of the others.
:::demo
```html
<el-skeleton :rows="5" />
```
:::
### Animation
We have provided a switch flag indicating whether showing the loading animation, called `animated` when this is true, all children of `el-skeleton` will show animation
:::demo
```html
<el-skeleton :rows="5" animated />
```
:::
### Customized Template
ElementPlus only provides the most common template, sometimes that could be a problem, so you have a slot named `template` to do that work.
Also we have provided different types skeleton unit that you can choose, for more detailed info, please scroll down to the bottom of this page to see the API description. Also, when building your own customized skeleton structure, you should be structuring them as closer to the real DOM as possible, which avoiding the DOM bouncing caused by the height difference.
:::demo
```html
<template>
<el-skeleton style="width: 240px">
<template #template>
<el-skeleton-item variant="image" style="width: 240px; height: 240px;" />
<div style="padding: 14px;">
<el-skeleton-item variant="p" style="width: 50%" />
<div
style="display: flex; align-items: center; justify-items: space-between;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
</el-skeleton>
</template>
```
:::
### Loading state
When `Loading` ends, we always need to show the real UI with data to our end users. with the attribtue `loading` we can control whether showing the DOM. You can also use slot `default` to structure the real DOM element.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated>
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Rendering a list of data
Most of the time, skeleton is used as indicators of rendering a list of data which haven't been fetched from server yet, then we need to create a list of skeleton out of no where to make it look like it is loading, with `count` attribute, you can control how many these templates you need to render to the browser.
:::tip
We do not recommend rendering lots of fake UI to the browser, it will still cause the performance issue, it also costs longer to destroy the skeleton. Keep `count` as small as it can be to make better user experience.
:::
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<el-button @click="setLoading">Click me to reload</el-button>
<el-skeleton style="width: 240px" :loading="loading" animated :count="3">
<template #template>
<el-skeleton-item
variant="image"
style="width: 400px; height: 267px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card
:body-style="{ padding: '0px', marginBottom: '1px' }"
v-for="item in lists"
>
<img :src="item.imgUrl" class="image multi-content" />
<div style="padding: 14px;">
<span>{{ item.name }}</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
lists: [],
}
},
mounted() {
this.loading = false
this.lists = [
{
imgUrl:
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
name: 'Deer',
},
{
imgUrl:
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
name: 'Horse',
},
{
imgUrl:
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
name: 'Mountain Lion',
},
]
},
methods: {
setLoading() {
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
},
})
</script>
```
:::
### Avoiding rendering bouncing.
Sometimes API responds very quickly, when that happens, the skeleton just gets rendered to the DOM then it needs to switch back to real DOM, that causes the sudden flashy. To avoid such thing, you can use the `throttle` attribute.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated :throttle="500">
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: false,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Skeleton Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| animated | whether showing the animation | boolean | true / false | false |
| count | how many fake items to render to the DOM | number | integer | 1 |
| loading | whether showing the real DOM | boolean | true / false | false |
| rows | numbers of the row, only useful when no template slot were given | number | integer | 3 |
| throttle | Rendering delay in millseconds | number | integer | 0 |
### Skeleton Item Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| variant | The current rendering skeleton type | Enum(string) | p / text / h1 / h3 / text / caption / button / image / circle / rect | text |
### Skeleton Slots
| Name | Description |
| ---- | ----------- |
| default | Real rendering DOM |
| template | Custom rendering skeleton template |

319
website/docs/es/skeleton.md Normal file
View File

@@ -0,0 +1,319 @@
## Skeleton
When loading data, and you need a rich experience for visual and interactions for your end users, you can choose `skeleton`.
### Basic usage
The basic skeleton.
:::demo
```html
<template>
<el-skeleton />
</template>
```
:::
### Configurable Rows
You can configure the row numbers yourself, for more precise rendering effect, the actual rendered row number will always be 1 row more than the given number, that is because we are rendering a title row with 33% width of the others.
:::demo
```html
<el-skeleton :rows="5" />
```
:::
### Animation
We have provided a switch flag indicating whether showing the loading animation, called `animated` when this is true, all children of `el-skeleton` will show animation
:::demo
```html
<el-skeleton :rows="5" animated />
```
:::
### Customized Template
ElementPlus only provides the most common template, sometimes that could be a problem, so you have a slot named `template` to do that work.
Also we have provided different types skeleton unit that you can choose, for more detailed info, please scroll down to the bottom of this page to see the API description. Also, when building your own customized skeleton structure, you should be structuring them as closer to the real DOM as possible, which avoiding the DOM bouncing caused by the height difference.
:::demo
```html
<template>
<el-skeleton style="width: 240px">
<template #template>
<el-skeleton-item variant="image" style="width: 240px; height: 240px;" />
<div style="padding: 14px;">
<el-skeleton-item variant="p" style="width: 50%" />
<div
style="display: flex; align-items: center; justify-items: space-between;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
</el-skeleton>
</template>
```
:::
### Loading state
When `Loading` ends, we always need to show the real UI with data to our end users. with the attribtue `loading` we can control whether showing the DOM. You can also use slot `default` to structure the real DOM element.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated>
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Rendering a list of data
Most of the time, skeleton is used as indicators of rendering a list of data which haven't been fetched from server yet, then we need to create a list of skeleton out of no where to make it look like it is loading, with `count` attribute, you can control how many these templates you need to render to the browser.
:::tip
We do not recommend rendering lots of fake UI to the browser, it will still cause the performance issue, it also costs longer to destroy the skeleton. Keep `count` as small as it can be to make better user experience.
:::
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<el-button @click="setLoading">Click me to reload</el-button>
<el-skeleton style="width: 240px" :loading="loading" animated :count="3">
<template #template>
<el-skeleton-item
variant="image"
style="width: 400px; height: 267px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card
:body-style="{ padding: '0px', marginBottom: '1px' }"
v-for="item in lists"
>
<img :src="item.imgUrl" class="image multi-content" />
<div style="padding: 14px;">
<span>{{ item.name }}</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
lists: [],
}
},
mounted() {
this.loading = false
this.lists = [
{
imgUrl:
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
name: 'Deer',
},
{
imgUrl:
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
name: 'Horse',
},
{
imgUrl:
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
name: 'Mountain Lion',
},
]
},
methods: {
setLoading() {
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
},
})
</script>
```
:::
### Avoiding rendering bouncing.
Sometimes API responds very quickly, when that happens, the skeleton just gets rendered to the DOM then it needs to switch back to real DOM, that causes the sudden flashy. To avoid such thing, you can use the `throttle` attribute.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated :throttle="500">
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: false,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Skeleton Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| animated | whether showing the animation | boolean | true / false | false |
| count | how many fake items to render to the DOM | number | integer | 1 |
| loading | whether showing the real DOM | boolean | true / false | false |
| rows | numbers of the row, only useful when no template slot were given | number | integer | 3 |
| throttle | Rendering delay in millseconds | number | integer | 0 |
### Skeleton Item Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| variant | The current rendering skeleton type | Enum(string) | p / text / h1 / h3 / text / caption / button / image / circle / rect | text |
### Skeleton Slots
| Name | Description |
| ---- | ----------- |
| default | Real rendering DOM |
| template | Custom rendering skeleton template |

View File

@@ -0,0 +1,319 @@
## Skeleton
When loading data, and you need a rich experience for visual and interactions for your end users, you can choose `skeleton`.
### Basic usage
The basic skeleton.
:::demo
```html
<template>
<el-skeleton />
</template>
```
:::
### Configurable Rows
You can configure the row numbers yourself, for more precise rendering effect, the actual rendered row number will always be 1 row more than the given number, that is because we are rendering a title row with 33% width of the others.
:::demo
```html
<el-skeleton :rows="5" />
```
:::
### Animation
We have provided a switch flag indicating whether showing the loading animation, called `animated` when this is true, all children of `el-skeleton` will show animation
:::demo
```html
<el-skeleton :rows="5" animated />
```
:::
### Customized Template
ElementPlus only provides the most common template, sometimes that could be a problem, so you have a slot named `template` to do that work.
Also we have provided different types skeleton unit that you can choose, for more detailed info, please scroll down to the bottom of this page to see the API description. Also, when building your own customized skeleton structure, you should be structuring them as closer to the real DOM as possible, which avoiding the DOM bouncing caused by the height difference.
:::demo
```html
<template>
<el-skeleton style="width: 240px">
<template #template>
<el-skeleton-item variant="image" style="width: 240px; height: 240px;" />
<div style="padding: 14px;">
<el-skeleton-item variant="p" style="width: 50%" />
<div
style="display: flex; align-items: center; justify-items: space-between;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
</el-skeleton>
</template>
```
:::
### Loading state
When `Loading` ends, we always need to show the real UI with data to our end users. with the attribtue `loading` we can control whether showing the DOM. You can also use slot `default` to structure the real DOM element.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated>
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Rendering a list of data
Most of the time, skeleton is used as indicators of rendering a list of data which haven't been fetched from server yet, then we need to create a list of skeleton out of no where to make it look like it is loading, with `count` attribute, you can control how many these templates you need to render to the browser.
:::tip
We do not recommend rendering lots of fake UI to the browser, it will still cause the performance issue, it also costs longer to destroy the skeleton. Keep `count` as small as it can be to make better user experience.
:::
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<el-button @click="setLoading">Click me to reload</el-button>
<el-skeleton style="width: 240px" :loading="loading" animated :count="3">
<template #template>
<el-skeleton-item
variant="image"
style="width: 400px; height: 267px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card
:body-style="{ padding: '0px', marginBottom: '1px' }"
v-for="item in lists"
>
<img :src="item.imgUrl" class="image multi-content" />
<div style="padding: 14px;">
<span>{{ item.name }}</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
lists: [],
}
},
mounted() {
this.loading = false
this.lists = [
{
imgUrl:
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
name: 'Deer',
},
{
imgUrl:
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
name: 'Horse',
},
{
imgUrl:
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
name: 'Mountain Lion',
},
]
},
methods: {
setLoading() {
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
},
})
</script>
```
:::
### Avoiding rendering bouncing.
Sometimes API responds very quickly, when that happens, the skeleton just gets rendered to the DOM then it needs to switch back to real DOM, that causes the sudden flashy. To avoid such thing, you can use the `throttle` attribute.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated :throttle="500">
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: false,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Skeleton Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| animated | whether showing the animation | boolean | true / false | false |
| count | how many fake items to render to the DOM | number | integer | 1 |
| loading | whether showing the real DOM | boolean | true / false | false |
| rows | numbers of the row, only useful when no template slot were given | number | integer | 3 |
| throttle | Rendering delay in millseconds | number | integer | 0 |
### Skeleton Item Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| variant | The current rendering skeleton type | Enum(string) | p / text / h1 / h3 / text / caption / button / image / circle / rect | text |
### Skeleton Slots
| Name | Description |
| ---- | ----------- |
| default | Real rendering DOM |
| template | Custom rendering skeleton template |

319
website/docs/jp/skeleton.md Normal file
View File

@@ -0,0 +1,319 @@
## Skeleton
When loading data, and you need a rich experience for visual and interactions for your end users, you can choose `skeleton`.
### Basic usage
The basic skeleton.
:::demo
```html
<template>
<el-skeleton />
</template>
```
:::
### Configurable Rows
You can configure the row numbers yourself, for more precise rendering effect, the actual rendered row number will always be 1 row more than the given number, that is because we are rendering a title row with 33% width of the others.
:::demo
```html
<el-skeleton :rows="5" />
```
:::
### Animation
We have provided a switch flag indicating whether showing the loading animation, called `animated` when this is true, all children of `el-skeleton` will show animation
:::demo
```html
<el-skeleton :rows="5" animated />
```
:::
### Customized Template
ElementPlus only provides the most common template, sometimes that could be a problem, so you have a slot named `template` to do that work.
Also we have provided different types skeleton unit that you can choose, for more detailed info, please scroll down to the bottom of this page to see the API description. Also, when building your own customized skeleton structure, you should be structuring them as closer to the real DOM as possible, which avoiding the DOM bouncing caused by the height difference.
:::demo
```html
<template>
<el-skeleton style="width: 240px">
<template #template>
<el-skeleton-item variant="image" style="width: 240px; height: 240px;" />
<div style="padding: 14px;">
<el-skeleton-item variant="p" style="width: 50%" />
<div
style="display: flex; align-items: center; justify-items: space-between;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
</el-skeleton>
</template>
```
:::
### Loading state
When `Loading` ends, we always need to show the real UI with data to our end users. with the attribtue `loading` we can control whether showing the DOM. You can also use slot `default` to structure the real DOM element.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated>
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Rendering a list of data
Most of the time, skeleton is used as indicators of rendering a list of data which haven't been fetched from server yet, then we need to create a list of skeleton out of no where to make it look like it is loading, with `count` attribute, you can control how many these templates you need to render to the browser.
:::tip
We do not recommend rendering lots of fake UI to the browser, it will still cause the performance issue, it also costs longer to destroy the skeleton. Keep `count` as small as it can be to make better user experience.
:::
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<el-button @click="setLoading">Click me to reload</el-button>
<el-skeleton style="width: 240px" :loading="loading" animated :count="3">
<template #template>
<el-skeleton-item
variant="image"
style="width: 400px; height: 267px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card
:body-style="{ padding: '0px', marginBottom: '1px' }"
v-for="item in lists"
>
<img :src="item.imgUrl" class="image multi-content" />
<div style="padding: 14px;">
<span>{{ item.name }}</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">Operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
lists: [],
}
},
mounted() {
this.loading = false
this.lists = [
{
imgUrl:
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
name: 'Deer',
},
{
imgUrl:
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
name: 'Horse',
},
{
imgUrl:
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
name: 'Mountain Lion',
},
]
},
methods: {
setLoading() {
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
},
})
</script>
```
:::
### Avoiding rendering bouncing.
Sometimes API responds very quickly, when that happens, the skeleton just gets rendered to the DOM then it needs to switch back to real DOM, that causes the sudden flashy. To avoid such thing, you can use the `throttle` attribute.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">Switch Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated :throttle="500">
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>Delicious hamberger</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">operation button</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: false,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Skeleton Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| animated | whether showing the animation | boolean | true / false | false |
| count | how many fake items to render to the DOM | number | integer | 1 |
| loading | whether showing the real DOM | boolean | true / false | false |
| rows | numbers of the row, only useful when no template slot were given | number | integer | 3 |
| throttle | Rendering delay in millseconds | number | integer | 0 |
### Skeleton Item Attributes
| Attribute | Description | Type | Acceptable Value | Default Value |
| ------- | ---------------- | ------- | ------------ | ------ |
| variant | The current rendering skeleton type | Enum(string) | p / text / h1 / h3 / text / caption / button / image / circle / rect | text |
### Skeleton Slots
| Name | Description |
| ---- | ----------- |
| default | Real rendering DOM |
| template | Custom rendering skeleton template |

View File

@@ -0,0 +1,325 @@
## Skeleton 骨架屏
在需要等待加载内容的位置设置一个骨架屏, 某些场景下比 Loading 的视觉效果更好。
### 基础用法
基础的骨架效果。
:::demo
```html
<template>
<el-skeleton />
</template>
```
:::
### 更多参数
可以配置骨架屏段落数量, 以便更接近真实渲染效果. 显示的数量会比传入的数量多 1, 首行会被渲染一个长度 33% 的段首.
:::demo
```html
<el-skeleton :rows="5" />
```
:::
### 动画效果
显示动画效果。
:::demo
```html
<el-skeleton :rows="5" animated />
```
:::
### 自定义样式
ElementPlus 提供的排版模式有时候并不满足要求, 当您想要用自己定义的模板时, 可以通过一个具名 Slot 来自己设定模板.
我们提供了不同的模板单元可供使用, 具体可选值请看 API 详细描述. 建议在描述模板的时候, 尽量靠近真实的 DOM 结构, 这样可以避免 DOM 高度差距引起的抖动.
:::demo
```html
<template>
<el-skeleton style="width: 240px">
<template #template>
<el-skeleton-item variant="image" style="width: 240px; height: 240px;" />
<div style="padding: 14px;">
<el-skeleton-item variant="p" style="width: 50%" />
<div
style="display: flex; align-items: center; justify-items: space-between;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
</el-skeleton>
</template>
```
:::
### Loading 状态
当 Loading 结束之后, 我们往往需要显示真实的 UI, 可以通过 `loading` 的值来控制是否显示真实的 DOM. 然后通过
具名 Slot 来设置当 loading 结束之后需要展示的 UI.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">切换 Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton style="width: 240px" :loading="loading" animated>
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>好吃的汉堡</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">操作按钮</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### 渲染多条数据
大多时候, 骨架屏都被用来渲染列表, 当我们需要在从服务器获取数据的时候来渲染一个假的 UI. 利用 `count` 这个属性就能控制渲染多少条假的数据在页面上
:::tip
请注意, 请尽可能的将 `count` 的大小保持在最小状态, 即使是假的 UI, DOM 元素多了之后, 照样会引起性能问题, 并且在骨架屏销毁时所消耗的时间也会更长(相对的).
:::
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<el-button @click="setLoading">点我重新加载</el-button>
<el-skeleton style="width: 240px" :loading="loading" animated :count="3">
<template #template>
<el-skeleton-item
variant="image"
style="width: 400px; height: 267px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card
:body-style="{ padding: '0px', marginBottom: '1px' }"
v-for="item in lists"
>
<img :src="item.imgUrl" class="image multi-content" />
<div style="padding: 14px;">
<span>{{ item.name }}</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">操作按钮</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: true,
currentDate: dayjs().format('YYYY-MM-DD'),
lists: [],
}
},
mounted() {
this.loading = false
this.lists = [
{
imgUrl:
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
name: '鹿',
},
{
imgUrl:
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
name: '马',
},
{
imgUrl:
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
name: '山狮',
},
]
},
methods: {
setLoading() {
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
},
})
</script>
```
:::
### 防止渲染抖动
有的时候, API 的请求回来的特别快, 往往骨架占位刚刚被渲染, 真实的数据就已经回来了, 用户的界面会突然一闪, 此时为了避免这种情况, 就需要通过 `throttle` 属性来避免这个问题.
:::demo
```html
<template>
<el-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px;">切换 Loading</label>
<el-switch v-model="loading" />
</div>
<el-skeleton
style="width: 240px"
:loading="loading"
animated
:throttle="500"
>
<template #template>
<el-skeleton-item
variant="image"
style="width: 240px; height: 240px;"
/>
<div style="padding: 14px;">
<el-skeleton-item variant="h3" style="width: 50%;" />
<div
style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px;"
>
<el-skeleton-item variant="text" style="margin-right: 16px;" />
<el-skeleton-item variant="text" style="width: 30%;" />
</div>
</div>
</template>
<template #default>
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px;">
<span>好吃的汉堡</span>
<div class="bottom clearfix">
<span class="time">{{ currentDate }}</span>
<el-button type="text" class="button">操作按钮</el-button>
</div>
</div>
</el-card>
</template>
</el-skeleton>
</el-space>
</template>
<script>
import { defineComponent } from 'vue'
const dayjs = require('dayjs')
export default defineComponent({
data() {
return {
loading: false,
currentDate: dayjs().format('YYYY-MM-DD'),
}
},
})
</script>
```
:::
### Skeleton Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| -------- | ------------------------------------------- | ------- | ------------ | ------ |
| animated | 是否使用动画 | boolean | true / false | false |
| count | 渲染多少个 template, 建议使用尽可能小的数字 | number | integer | 1 |
| loading | 是否显示真实的 DOM 结构 | boolean | true / false | false |
| rows | 骨架屏段落数量 | number | 正整数 | 3 |
| throttle | 延迟占位 DOM 渲染的时间, 单位是毫秒 | number | 正整数 | 0 |
### Skeleton Item Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------- | ------------------------ | ------------ | -------------------------------------------------------------------- | ------ |
| variant | 当前显示的占位元素的样式 | Enum(string) | p / text / h1 / h3 / text / caption / button / image / circle / rect | text |
### Skeleton Slots
| name | description |
| -------- | -------------------- |
| default |  用来展示真实 UI |
| template | 用来展示自定义占位符 |

View File

@@ -184,6 +184,10 @@
{
"path": "/avatar",
"title": "Avatar 头像"
},
{
"path": "/skeleton",
"title": "Skeleton 骨架屏"
}
]
},
@@ -486,6 +490,10 @@
{
"path": "/badge",
"title": "Badge"
},
{
"path": "/skeleton",
"title": "Skeleton"
}
]
},
@@ -792,6 +800,10 @@
{
"path": "/badge",
"title": "Badge"
},
{
"path": "/skeleton",
"title": "Skeleton"
}
]
},
@@ -1098,6 +1110,10 @@
{
"path": "/badge",
"title": "Badge"
},
{
"path": "/skeleton",
"title": "Skeleton"
}
]
},
@@ -1404,6 +1420,10 @@
{
"path": "/badge",
"title": "Badge"
},
{
"path": "/skeleton",
"title": "Skeleton"
}
]
},