注意:如果同学们不使用page-skeleton-webpack-plugin和vue-server-renderer,并且,你希望生成的骨架屏可以和页面相匹配,可以往下看!!!
1、实现原理:
给需要的dom标签设置特定类名,使用getBoundingClientRect来获取dom标签的大小和位置信息,并以此来生成一个占位符,最终生成一份“骨架屏”,当页面加载完毕,移除该“骨架屏”组件即可。因为这是在mounted周期获取元素信息,故如果元素信息不满意,需要先默认一些初始数据
2、代码如下:
(1) index.vue:
<template>
<div class='skeleton' :style="skeletonStyle">
<div v-for="(item,index) in darks"
class="item dark"
:style="{ ...createStyle(item), backgroundColor: darkColor }"
:key="'darks'+index">
</div>
<div v-for="(item,index) in lights"
class="item light"
:style="{ ...createStyle(item), backgroundColor: lightColor }"
:key="'lights'+index">
</div>
<div v-for="(item,index) in squares"
class="item square"
:style="createStyle(item)"
:key="'squares'+index">
</div>
<div v-for="(item,index) in circulars"
class="item circular"
:style="createStyle(item)"
:key="'circulars'+index">
</div>
<div v-for="(item,index) in cylinders"
class="item cylinder"
:style="createCylinderStyle(item)"
:key="'circulars'+index">
</div>
</div>
</template>
<script>
// 约定.skeleton样式类为骨架屏查找绘制节点的根节点
// 约定.skeleton-square 样式类,表示绘制当前节点的骨架节点样式为方形(如商品卡片)
// 约定.skeleton-circular样式类,表示绘制当前节点的骨架节点为圆形(如logo)
// 约定.skeleton-cylinder样式类,表示绘制当前节点的骨架节点为长条形(如搜索框)
// 约定.skeleton-light与.skeleton-dark为块元素背景骨架样式
export default {
name: "Skeleton",
data(){
return{
lights:[],
darks: [],
squares: [],
circulars: [],
cylinders: [],
}
},
props:{
selector: {
type:String,
required:false,
default:'skeleton'
},
backgroundColor: {
type:String,
required:false,
default:'#fff'
},
lightColor: {
type:String,
required:false,
default:'white'
},
darkColor: {
type:String,
required:false,
default:'#2f3333'
},
top: {
type:String,
required:false,
default:'0'
},
},
mounted() {
Promise.all([
this.selectAll(`.${this.selector}-light`),
this.selectAll(`.${this.selector}-dark`),
this.selectAll(`.${this.selector}-square`),
this.selectAll(`.${this.selector}-circular`),
this.selectAll(`.${this.selector}-cylinder`),
]).then(([lights, darks, squares, circulars, cylinders]) =>{
this.lights=lights
this.darks=darks
this.squares=squares
this.circulars=circulars
this.cylinders=cylinders
}
)
},
computed:{
skeletonStyle(){
return {
backgroundColor:this.backgroundColor,
top:this.top
}
}
},
methods: {
selectAll(selector) {
return new Promise(resolve =>{
let domList = document.querySelectorAll(selector)
let resultList = []
for(let a=0;a<domList.length;a++){
resultList.push(domList[a].getBoundingClientRect())
}
console.log('domList',domList)
resolve(resultList)
}
)
},
createStyle({ width, height, top, left }){
return {
width: `${width}px`,
height: `${height}px`,
top: `${top}px`,
left: `${left}px`,
}
},
createCylinderStyle(rect){
return {
...this.createStyle(rect),
'border-radius': `${rect.height / 2}px`,
}
}
}
}
</script>
<style scoped lang="scss">
@import "./index";
</style>
(2)index.scss
$max-index: 1000;
$skeleton-index: $max-index - 3;
$skeleton-item-index: $skeleton-index + 2;
$skeleton-container-index: $skeleton-index + 1;
$grey-background-color: #f5f5f5;
$border-radius: 8px;
.skeleton {
position: fixed;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100vh;
z-index: 9997;
overflow: hidden;
.item {
position: fixed;
display: inline-block;
background: $grey-background-color;
z-index: $skeleton-item-index;
&.dark,
&.light {
z-index: $skeleton-container-index;
}
&.square {
border-radius: $border-radius;
}
&.circular {
border-radius: 50%;
}
}
}
3、使用demo
<template>
<div class="home-wrapper">
<!--仅仅移除骨架屏组件即可-->
<vueSkeleton v-if="loading"></vueSkeleton>
<div class="banner skeleton-square">
<img :src="banner" alt="">
</div>
<div class="logo-wrapper">
<div class="logo skeleton-circular" v-for="a in 3" :key="'logo'+a"></div>
</div>
<div class="content"></div>
</div>
</template>
<script type="text/ecmascript-6">
import vueSkeleton from './index'
export default {
name:'vueSkeleton',
components:{vueSkeleton},
data(){
return{
loading:true,
// 如有需要,先默认数据以撑开元素大小和位置
banner:'http://img5.imgtn.bdimg.com/it/u=1768436016,3486987531&fm=26&gp=0.jpg',
}
},
created() {
this.getData()
},
methods:{
getData(){
setTimeout(()=>{
this.loading=false
},300)
}
}
}
</script>
<style lang="scss" scoped>
@import "./index";
.home-wrapper{
width: 100%;
.banner{
width: 100%;
height: 175px;
}
.logo-wrapper{
width: 100%;
padding: 0 15px;
box-sizing: border-box;
display: flex;
.logo{
border-radius: 50%;
width: 105px;
height: 105px;
margin-right: 15px;
&:last-child{
margin-right: 0;
}
}
}
.content{
width: 100%;
height: 300px;
}
}
</style>
4、Taro骨架屏也写了一下
https://blog.csdn.net/u010214074/article/details/90120727