【浏览器】骨架屏

前后端分离以来,首屏渲染时间(FCP)因为首屏需要请求更多内容,比原来多了更多 HTTP 的往返时间(RTT),这造成了白屏,如果白屏时间过长,用户体验会大打折扣。页面交互优化方式近年来在不断发展,页面等待加载的优化大致有以下几个阶段:

》原始做法:html的div边框(约等于什么都没做);

》优化做法:loading小菊花;

》进阶做法:FCP 优化;

》最新做法:骨架屏。

以下主要介绍骨架屏。在骨架屏展开论述之前,我们先回顾以下FCP的优化。

FCP优化(First Contentful Paint)

加速或减少HTTP请求损耗

延迟加载

减少请求内容的体积

优化用户等待体验

骨架屏

相比于之前的Loading动画,骨架屏页面更容易让用户产生一种错觉,页面快加载完了。骨架屏实现原理很简单,就是通过占位线框元素,渐进式加载数据。

大厂骨架屏案例:

参见: https://zhuanlan.zhihu.com/p/96455668

1. facebook将用户固定的头像,author,日期和一小部分文字作为骨架主体

2. jira则是标题和logo对应的很整齐

3. linkedin可以说完全没有对齐,而是使用一种更加的展示骨架布局

4. slack则是使用混合的loading方式,有骨架图也有旋转圆,不仅如此,slack并没有全部使用同一种灰色值,不同的block的颜色代表的该区域的字体颜色,这又是一种切换顺滑度的提升。

这里要介绍的就是优化用户等待体验的骨架屏。

生成骨架屏的方法

生成骨架屏的方式主要有:

手写HTML、CSS的方式为目标页定制骨架屏(参考:https://segmentfault.com/a/1190000014832185),主要思路就是使用 vue-server-renderer 这个本来用于服务端渲染的插件,用来把我们写的.vue文件处理为HTML,插入到页面模板的挂载点中,完成骨架屏的注入。这种方式不甚友好,如果页面样式改变了,还得改一遍骨架屏,增加了维护成本。

使用图片作为骨架屏;简单暴力,让 UI 同学花点功夫吧哈哈;小米商城的移动端页面采用的就是这个方法,它是使用了一个 Base64 的图片来作为骨架屏。

插件自动生成并自动插入静态骨架屏;这种方法跟第一种方法类似,不过是自动生成骨架屏,可以关注下饿了么开源的插件 page-skeleton-webpack-plugin,它根据项目中不同的路由页面生成相应的骨架屏页面,并将骨架屏页面通过 webpack 打包到对应的静态路由页面中,不过要注意的是这个插件目前只支持history方式的路由,不支持hash方式,且目前只支持首页的骨架屏,并没有组件级的局部骨架屏实现,作者说以后会有计划实现。

目前市面上用得比较多的是下面这几个插件:

相关插件配置方法参见: https://www.jianshu.com/p/eacac700630e

1. vue-server-renderer   

2. vue-skeleton-webpack-plugin

3. page-skeleton-webpack-plugin      基于vue-cli脚手架,饿了么团队出的

简单实现骨架屏的代码:

<!DOCTYPE html>

<html>

  <head>

    <meta charset="UTF-8" />

    <title>Skeletons</title>

    <style>

      img {

        width: 100%;

      }

      .media-box-img {

        width: 60px;

        height: 60px;

      }

      /* 阻止Skeletons点击事件 */

      .pointer-stop {

        pointer-events: none;

      }

      /* Skeletons效果 */

      .skeletons {

        position: relative;

        display: block;

        overflow: hidden;

        height: 100%;

        min-height: 20px;

        background-color: #ededed;

      }

      .skeletons:empty::after {    // 巧妙利用了css的empty伪类选择器

        display: block;

        content: '';

        position: absolute;

        width: 100%;

        height: 100%;

        -webkit-transform: translateX(-100%);

        transform: translateX(-100%);

        background: linear-gradient(90deg, transparent, rgba(216, 216, 216, 0.753), transparent);

        -webkit-animation: loading 1.5s infinite;

        animation: loading 1.5s infinite;

      }

      @keyframes loading {

        100% {

          -webkit-transform: translateX(100%);

          transform: translateX(100%);

        }

      }

    </style>

  </head>

  <body>

    <div class="weui-panel weui-panel_access">

      <div class="weui-panel__bd">

        <a href="javascript:void(0);" class="weui-media-box weui-media-box_appmsg pointer-stop">

          <div class="weui-media-box__hd">

            <div class="media-box-img skeletons"></div>

          </div>

          <div class="weui-media-box__bd">

            <div class="weui-media-box__title skeletons"></div>

            <p class="weui-media-box__desc">

              <span class="media-box-desc skeletons"></span>

            </p>

          </div>

        </a>

      </div>

    </div>

    <script>

      function renderCard() {

        var cardImage = document.querySelector('.weui-panel-title')

        cardImage.textContent = '标题'

        cardImage.classList.remove('skeletons')

        var listData = [

          {

            img:xxx.jpg,

            desc: '内容内容内容内容'

          }

        ]

        var html = ''

        var cardImage1 = document.querySelectorAll('.media-box-img')

        var cardImage2 = document.querySelectorAll('.weui-media-box')

        var cardImage3 = document.querySelectorAll('.weui-media-box__title')

        var cardImage4 = document.querySelectorAll('.media-box-desc')

        for (var i = 0; i < listData.length; i++) {

          cardImage2[i].classList.remove('pointer-stop')

          cardImage1[i].classList.remove('skeletons')

          cardImage3[i].classList.remove('skeletons')

          cardImage4[i].classList.remove('skeletons')

          cardImage1[i].innerHTML = "<img src='" + listData[i].img + "' />"

          cardImage3[i].innerHTML = '一段标题'

          cardImage4[i].innerHTML = '一段描述'

        }

      }

      setTimeout(function() {

        renderCard()

      }, 4000)

    </script>

  </body>

</html>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容