一. 骨架屏简介
简单来说, 骨架屏就是填充了背景等特效的真实页面手稿轮廓图。 它可以是精确/粗略的描述了页面各个元素大小,形状,位置占位的一种页面真实数据渲染加载前的排版。 目的是加载页面过程中给用户一种较好体验的过渡效果,降低用户的焦灼情绪。避免页面过大/网络过慢长时间白屏或者闪烁。
二. 骨架屏的实现方案
目前生成骨架屏的技术方案大概有三种:
- 使用图片,svg 或者手动编写骨架屏代码: 使用 HTML + CSS 的方式可以很快的完成骨架屏效果, 但是面对视觉设计的改变及需求的更迭, 对骨架屏的跟进修改会非常被动, 这种机械化重复劳作的方式就显得机动性不足;
- 通过预渲染手动书写的代码生成相应的骨架屏: 该方案做的比较成熟的是 vue-skeleton-webpack-plugin , 通过vueSSR 结合 webpack 在构建时渲染写好的vue 骨架屏组件, 将预渲染生成的Dom 节点和相关样式插入到最终输出的 HTML 中;
(该方案与vue 技术直接关联, 在当今前端架构三分天下的环境下,不是一个很灵活,可控的方案) - 饿了么内部的生成骨架页面工具: 该方案通过一个webpack 插件 page-skeleton-webpack-plugin 的方式与项目开发无缝集成, 属于自动生成骨架屏方面做得非常完善的一种。并且可以启动UI界面专门调整骨架屏, 但是在面对复杂的页面也会有不尽人意的地方,他生成的骨架屏节点是基于页面本身的结构和css, 存在嵌套比较深的情况, 体积不会太小, 并且只支持history 模式。
如果你的项目Webpack构建,且非SSR:
四. 一个基于方案二没有使用任何第三方骨架屏插件的粗暴快捷方案
方案前提:
既然是解决首屏效果。 考虑到效率和成本, 骨架屏给大致轮廓即可,忽略细节上的设计。
方案设计
一般开发流程是这样的: 给出设计稿/静态html -> 开发 -> 测试 -> …
其中设计稿/静态HTML, 可能是UED手写/第三方UI库 。
这里我们的方案是:
- 开发一个提供:占位标签动画, 背景色及关闭骨架屏开关等属性的组件;
- 根据个人需求,在需要展示骨架屏效果的页面/标签上设置组件提供的属性即可;
- 通过vue ssr server build后, 会自动生成相关静态标签/css 到其配置文件中(这是webpack自动做的知道即可);
这里在设置占位高度有个小技巧: min-height
注: 本方案基于VUE SSR, 这里不对SSR的相关实现及设计做讲解。只要你的项目已支持SSR 即可。
方案优点
- 简单快捷;
- 依赖少;
- 门槛低;
- 较灵活;
- 控制权交给开发者;
- 支持自定义骨架屏展示时间;
- 支持根据实际数据返回,控制骨架屏展示
方案缺点
- 需要根据自己的需求,手动设置标签盒子的背景色/高/宽度;
走进源码及实例
- 骨架屏组件源码:
<template>
<div :class="[openSkeleton? 'skeleton-animate':'']">
<slot :openSkeleton="openSkeleton"></slot>
</div>
</template>
<script>
// 骨架屏计时器
let skeletonTimeoutIns = null
@Component
export default class Dialoglogin extends Vue {
// 手动控制骨架屏 关闭
@Prop({
default: false
})
closeSkeleton
// 骨架屏自动关闭秒数 (3秒: 3000 关闭)
@Prop({
default: 1000
})
timeOutSecond
// 是否为自动模式(setTimeout 来自动控制开/关)
@Prop({
default: false
})
isAuto
@Watch('isAuto', {
immediate: true
})
watchIsAuto(newVal, oldVal) {
if (!!newVal) {
!!skeletonTimeoutIns && clearTimeout(skeletonTimeoutIns)
skeletonTimeoutIns = setTimeout(() => {
this.openSkeleton = false
}, this.timeOutSecond)
} else {
clearTimeout(skeletonTimeoutIns)
skeletonTimeoutIns = null
}
}
@Watch('closeSkeleton', {
immediate: true
})
watchCloseSkeleton(newVal, oldVal) {
if (!!newVal) {
clearTimeout(skeletonTimeoutIns)
skeletonTimeoutIns = null
this.openSkeleton = false
}
}
// 开启骨架屏
openSkeleton = true
mounted() {}
}
</script>
<style scoped>
.skeleton-animate {
-webkit-animation: van-skeleton-blink 1.2s ease-in-out infinite;
animation: van-skeleton-blink 1.2s ease-in-out infinite;
}
.skeleton-bgcolor {
background-color: rgb(231, 230, 230);
/*#f2f3f5;*/
}
</style>
上面即为骨架屏的主要源码, 优化空间还是很大的,比如开放背景色API, 动画API 等等。
- 在页面中使用骨架屏组件
<template>
.....................
<SkeletonAll v-slot:default="slotProps" :is-auto="true" :time-out-second="4000">
<div class="special skeleton-bgcolor" style="margin-bottom: 6px; border-top: 1px solid #eee; min-height: 200px;">
<van-tabs v-model="activeSpecial" animated v-show="!slotProps.openSkeleton">
<van-tab style="height:200px" v-for="(item, index) in specialInfoList" :key="index" :title="item.title">
<van-image style="width:100%; height: 211px;" :src="item.img" @click="handleTabsItemClick">
<template v-slot:loading>
<van-loading type="spinner" size="30" color="#1989fa" />
</template>
</van-image>
</van-tab>
</van-tabs>
</div>
</SkeletonAll>
......................................
</template>
<script>
.........
import SkeletonAll from '@/components/skeleton-all'
.........
</script>
细心的同学应该留意到, 这里在骨架屏标签盒子上控制切换用的是 v-show 而非 v-if 。 为什么? 哈哈~
最终实现效果
骨架屏:
加载数据后: