在 Vue 应用中,如何实现骨架屏(Skeleton Screen)或加载占位符,提升用户感知性能?

各位观众老爷,大家好!今天咱们来聊聊Vue应用里的“骨架屏”,也就是Skeleton Screen。这玩意儿就像化妆前的素颜霜,能让你的应用在数据还没加载出来的时候,看起来不那么“裸奔”,提升用户体验。

开场白:为什么要用骨架屏?

话说回来,为啥要搞这么个东西?想象一下,你点开一个网页,一片空白,左等右等,数据才慢悠悠地蹦出来。用户的心情是不是像坐过山车?

骨架屏的作用就是在这段时间里,给用户一个“正在加载”的预期,而不是让他们面对空荡荡的屏幕。它能大大缓解用户的焦虑,让他们觉得应用运行速度很快。这就像餐厅门口摆放的样品菜,告诉顾客“别急,好吃的马上来!”

第一部分:骨架屏的实现思路

骨架屏的本质就是用一些占位元素,模拟真实数据的显示效果。它通常由灰色块、线条、圆形等组成,看起来像一个未完成的页面。

实现骨架屏主要有以下几种思路:

  1. 纯CSS方案: 利用CSS的动画和渐变,创建模拟加载效果的元素。
  2. Vue组件方案: 创建一个独立的Vue组件,用于渲染骨架屏。
  3. 插件方案: 使用现成的Vue骨架屏插件,例如vue-skeleton-loadervue-content-placeholders等。

接下来,咱们分别看看这几种方案的具体实现。

第二部分:纯CSS方案:简单粗暴但有效

纯CSS方案的优点是简单易懂,不需要引入额外的依赖。缺点是灵活性较低,难以适应复杂的页面结构。

示例代码:

<template>
  <div class="container">
    <div class="skeleton-item" v-if="loading">
      <div class="skeleton-avatar"></div>
      <div class="skeleton-title"></div>
      <div class="skeleton-content"></div>
      <div class="skeleton-content"></div>
    </div>
    <div class="real-content" v-else>
      <img :src="avatar" alt="avatar">
      <h1>{{ title }}</h1>
      <p>{{ content }}</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loading: true,
      avatar: '',
      title: '',
      content: ''
    };
  },
  mounted() {
    // 模拟数据加载
    setTimeout(() => {
      this.loading = false;
      this.avatar = 'https://example.com/avatar.jpg';
      this.title = '这是一个标题';
      this.content = '这是一段内容,用来测试骨架屏效果。';
    }, 2000);
  }
};
</script>

<style scoped>
.container {
  width: 500px;
  margin: 20px auto;
  border: 1px solid #ccc;
  padding: 20px;
}

.skeleton-item {
  display: flex;
  flex-direction: column;
}

.skeleton-avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background-color: #eee;
  margin-bottom: 10px;
  animation: skeleton-loading 1.5s linear infinite;
}

.skeleton-title {
  width: 70%;
  height: 20px;
  background-color: #eee;
  margin-bottom: 10px;
  animation: skeleton-loading 1.5s linear infinite;
}

.skeleton-content {
  width: 90%;
  height: 16px;
  background-color: #eee;
  margin-bottom: 5px;
  animation: skeleton-loading 1.5s linear infinite;
}

.real-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.real-content img {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin-bottom: 10px;
}

@keyframes skeleton-loading {
  0% {
    background-color: #eee;
  }
  50% {
    background-color: #ddd;
  }
  100% {
    background-color: #eee;
  }
}
</style>

代码解释:

  • loading:一个布尔值,用于控制显示骨架屏还是真实内容。
  • skeleton-item:骨架屏的容器。
  • skeleton-avatarskeleton-titleskeleton-content:骨架屏的各个占位元素。
  • @keyframes skeleton-loading:CSS动画,用于创建加载效果。

这个例子中,我们使用简单的灰色块模拟了头像、标题和内容,并使用CSS动画让它们闪烁,模拟加载过程。

第三部分:Vue组件方案:灵活可复用

Vue组件方案的优点是灵活性高,可以根据不同的页面结构定制骨架屏。缺点是需要编写额外的Vue组件。

示例代码:

首先,创建一个名为Skeleton.vue的组件:

<template>
  <div class="skeleton">
    <div class="skeleton-item" v-for="n in count" :key="n">
      <div class="skeleton-avatar" v-if="showAvatar"></div>
      <div class="skeleton-title" v-if="showTitle"></div>
      <div class="skeleton-content" v-for="i in contentLines" :key="i"></div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    count: {
      type: Number,
      default: 1
    },
    showAvatar: {
      type: Boolean,
      default: true
    },
    showTitle: {
      type: Boolean,
      default: true
    },
    contentLines: {
      type: Number,
      default: 2
    }
  }
};
</script>

<style scoped>
.skeleton {
  display: flex;
  flex-direction: column;
}

.skeleton-item {
  display: flex;
  flex-direction: column;
  margin-bottom: 10px;
}

.skeleton-avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background-color: #eee;
  margin-bottom: 10px;
  animation: skeleton-loading 1.5s linear infinite;
}

.skeleton-title {
  width: 70%;
  height: 20px;
  background-color: #eee;
  margin-bottom: 10px;
  animation: skeleton-loading 1.5s linear infinite;
}

.skeleton-content {
  width: 90%;
  height: 16px;
  background-color: #eee;
  margin-bottom: 5px;
  animation: skeleton-loading 1.5s linear infinite;
}

@keyframes skeleton-loading {
  0% {
    background-color: #eee;
  }
  50% {
    background-color: #ddd;
  }
  100% {
    background-color: #eee;
  }
}
</style>

然后,在你的组件中使用它:

<template>
  <div class="container">
    <Skeleton v-if="loading" :count="3" :showAvatar="false" :contentLines="3"/>
    <div class="real-content" v-else>
      <div v-for="item in data" :key="item.id">
        <img :src="item.avatar" alt="avatar">
        <h1>{{ item.title }}</h1>
        <p>{{ item.content }}</p>
      </div>
    </div>
  </div>
</template>

<script>
import Skeleton from './Skeleton.vue';

export default {
  components: {
    Skeleton
  },
  data() {
    return {
      loading: true,
      data: []
    };
  },
  mounted() {
    // 模拟数据加载
    setTimeout(() => {
      this.loading = false;
      this.data = [
        { id: 1, avatar: 'https://example.com/avatar1.jpg', title: '标题1', content: '内容1' },
        { id: 2, avatar: 'https://example.com/avatar2.jpg', title: '标题2', content: '内容2' },
        { id: 3, avatar: 'https://example.com/avatar3.jpg', title: '标题3', content: '内容3' }
      ];
    }, 2000);
  }
};
</script>

<style scoped>
.container {
  width: 500px;
  margin: 20px auto;
  border: 1px solid #ccc;
  padding: 20px;
}

.real-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.real-content img {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin-bottom: 10px;
}
</style>

代码解释:

  • Skeleton.vue:一个通用的骨架屏组件,接受countshowAvatarshowTitlecontentLines等props,用于定制骨架屏的显示效果。
  • 在父组件中,我们通过v-if指令控制显示骨架屏还是真实内容,并根据需要传递不同的props给Skeleton组件。

这种方案的优点是可以复用Skeleton组件,并在不同的页面中使用不同的配置,从而实现更灵活的骨架屏效果。

第四部分:插件方案:方便快捷但可能不够灵活

插件方案的优点是方便快捷,可以快速实现骨架屏效果。缺点是可能不够灵活,难以满足复杂的定制需求。

示例代码:

这里以vue-skeleton-loader为例:

  1. 安装插件:

    npm install vue-skeleton-loader
  2. main.js中注册插件:

    import Vue from 'vue';
    import App from './App.vue';
    import VueSkeletonLoader from 'vue-skeleton-loader';
    
    Vue.use(VueSkeletonLoader);
    
    new Vue({
      render: h => h(App),
    }).$mount('#app');
  3. 在你的组件中使用它:

    <template>
      <div class="container">
        <vue-skeleton-loader v-if="loading" type="list" :item-count="3">
            <template #avatar>
                <div style="width: 80px; height: 80px; border-radius: 50%;"></div>
            </template>
            <template #title>
                <div style="width: 70%; height: 20px;"></div>
            </template>
            <template #text>
                <div style="width: 90%; height: 16px;"></div>
                <div style="width: 90%; height: 16px;"></div>
            </template>
        </vue-skeleton-loader>
    
        <div class="real-content" v-else>
          <div v-for="item in data" :key="item.id">
            <img :src="item.avatar" alt="avatar">
            <h1>{{ item.title }}</h1>
            <p>{{ item.content }}</p>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          loading: true,
          data: []
        };
      },
      mounted() {
        // 模拟数据加载
        setTimeout(() => {
          this.loading = false;
          this.data = [
            { id: 1, avatar: 'https://example.com/avatar1.jpg', title: '标题1', content: '内容1' },
            { id: 2, avatar: 'https://example.com/avatar2.jpg', title: '标题2', content: '内容2' },
            { id: 3, avatar: 'https://example.com/avatar3.jpg', title: '标题3', content: '内容3' }
          ];
        }, 2000);
      }
    };
    </script>
    
    <style scoped>
    .container {
      width: 500px;
      margin: 20px auto;
      border: 1px solid #ccc;
      padding: 20px;
    }
    
    .real-content {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    
    .real-content img {
      width: 80px;
      height: 80px;
      border-radius: 50%;
      margin-bottom: 10px;
    }
    </style>

代码解释:

  • vue-skeleton-loader:插件提供的骨架屏组件。
  • type:指定骨架屏的类型,例如listcard等。
  • item-count:指定骨架屏的数量。
  • 使用templateslot来自定义骨架屏的形状和样式。

这种方案的优点是使用简单,可以快速实现骨架屏效果。缺点是灵活性较低,可能难以满足复杂的定制需求。

第五部分:骨架屏的最佳实践

  • 尽量模拟真实内容: 骨架屏应该尽量模拟真实内容的显示效果,让用户对加载完成后的页面有一个大致的预期。
  • 使用动画效果: 适当的动画效果可以增强骨架屏的视觉吸引力,让用户觉得应用正在加载。
  • 避免过度使用: 骨架屏应该只在必要的时候使用,避免过度使用,影响用户体验。如果数据加载速度很快,可以不用骨架屏,直接显示真实内容。
  • 考虑不同设备的适配: 骨架屏应该在不同的设备上显示效果一致,避免出现错位、变形等问题。
  • 与服务端渲染结合: 如果你的应用使用了服务端渲染,可以在服务端生成骨架屏的HTML,然后将其发送给客户端,这样可以减少客户端的渲染时间,提升用户体验。

总结:选择合适的方案

方案 优点 缺点 适用场景
纯CSS 简单易懂,不需要引入额外依赖 灵活性较低,难以适应复杂的页面结构 简单的页面结构,对定制需求不高
Vue组件 灵活性高,可以根据不同的页面结构定制骨架屏,可复用 需要编写额外的Vue组件 需要定制骨架屏显示效果,需要在多个页面中使用
插件 方便快捷,可以快速实现骨架屏效果 可能不够灵活,难以满足复杂的定制需求 快速实现骨架屏效果,对定制需求不高
服务端渲染 首屏加载速度快,用户体验好 需要服务端支持,配置复杂 对首屏加载速度要求高,服务端支持

选择哪种方案取决于你的项目需求和技术栈。如果你的页面结构比较简单,对定制需求不高,可以选择纯CSS方案。如果你的页面结构比较复杂,需要定制骨架屏显示效果,可以选择Vue组件方案。如果你的项目需要快速实现骨架屏效果,可以选择插件方案。如果你的应用使用了服务端渲染,可以考虑与服务端渲染结合。

结束语:提升用户体验,从骨架屏开始!

好了,今天的骨架屏讲座就到这里。希望大家能够掌握骨架屏的实现方法,并在自己的Vue应用中使用它,提升用户体验!记住,用户体验是王道!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注