Uniapp 组件级骨架屏与图片懒加载实战:提升首屏加载体验

2026-06-14 0 233

一、为什么首屏体验总是不尽如人意?

你一定遇到过这样的场景:用户打开一个小程序或H5页面,看到的是短暂的白屏,然后突然冒出一堆已经排好版的内容。或者,列表里的图片一张接一张地加载,页面不断跳动。这两种情况都会让用户觉得“慢”甚至“卡”。其实,很多时候接口请求并不慢,真正让体验变差的是内容加载过程中的视觉空白和布局抖动。

解决这个问题最直接的手法就是两样东西:骨架屏图片懒加载。骨架屏在真实内容出来之前用灰色区块撑住位置,告诉用户“这里即将有东西”;懒加载则让那些屏幕外的图片先不请求,等滚到它面前时再加载,把带宽和内存留给真正看得见的区域。Uniapp本身并没有内置骨架屏组件,但我们可以用自定义组件和条件渲染轻松造一个出来。懒加载则有现成的底层API可以调用,不需要第三方库。这篇文章就以一个新闻列表为例,从头到尾把这两个方案串起来。

二、骨架屏组件的设计思路

骨架屏要模拟真实内容的布局轮廓,一般用灰色或浅色块表示标题、图片、文本行。在Uniapp里,我们可以把它封装成一个独立组件,接收一些参数来控制“骨架”的形态,比如是否有头像、图片是横图还是竖图、文本的行数等。这样就做到了可复用:列表页、详情页、卡片组件都能用同一个骨架屏,只是变化几个参数。

骨架屏的实现原理并不复杂:在数据未回来时,用 v-if 显示一组占位块,数据就绪后再切到真实内容。为了保证切换时不引起高度突变,骨架屏的占位块尺寸要和最终内容大致一致。

我们从一个简单实例开始:一个常见的文章卡片,左侧有一张小图,右侧是标题和两行摘要。骨架屏就对应生成:左边一个正方形的灰色块,右边上面一个长条代表标题,下面两个短条代表摘要。下面直接看代码。

<!-- components/SkeletonCard.vue -->
<template>
    <view class="skeleton-card">
        <view class="skeleton-image"></view>
        <view class="skeleton-info">
            <view class="skeleton-title"></view>
            <view class="skeleton-line short"></view>
            <view class="skeleton-line medium"></view>
        </view>
    </view>
</template>

<script>
export default {
    name: 'SkeletonCard'
}
</script>

对应的样式(这里只描述规则,实际写在组件的 <style> 块里):这三个骨架元素都用灰色的圆角矩形,加上一个从左到右移动的高光亮片动画,模仿数据正在加载的感觉。骨架图片固定宽高与真实图片一致,标题条高度约等于实际标题的行高,两个摘要条略短。循环动画用 CSS 的 @keyframes 实现,具体细节下节展示。

三、给骨架加上“呼吸”效果

为了让骨架屏看起来不那么死板,一条常见的做法是让它有一层流动的光感,就像Facebook的骨架屏那样。这可以通过一个半透明的渐变在灰色块上水平滑动来实现。在Uniapp中,我们同样可以用CSS动画。在骨架屏组件的样式里写:

.skeleton-image, .skeleton-title, .skeleton-line {
    background: linear-gradient(
        90deg,
        #e8e8e8 25%,
        #f5f5f5 50%,
        #e8e8e8 75%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
    border-radius: 4px;
}

@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

这样,灰色块上就有一道高光从左扫到右,看起来像是数据正在被一点点拉取。这个动画对性能影响很小,Chrome 和 App 端都跑得很流畅。

四、在新闻列表中集成骨架屏

有了骨架屏组件,我们只需要在列表页的数据请求阶段用它来占位。假设我们有一个 NewsList 页面,在 onLoadmounted 时发起请求,在请求完成前显示骨架屏卡片。

<template>
    <view class="news-list">
        <block v-if="loading">
            <SkeletonCard v-for="n in 5" :key="n" />
        </block>
        <block v-else>
            <view class="news-item" v-for="item in list" :key="item.id">
                <image class="news-img" :src="item.thumb" mode="aspectFill" lazy-load="true"></image>
                <view class="news-content">
                    <text class="news-title">{{ item.title }}</text>
                    <text class="news-desc">{{ item.summary }}</text>
                </view>
            </view>
        </block>
    </view>
</template>

<script>
import SkeletonCard from '@/components/SkeletonCard.vue';
export default {
    components: { SkeletonCard },
    data() {
        return {
            loading: true,
            list: []
        }
    },
    onLoad() {
        this.fetchNews();
    },
    methods: {
        async fetchNews() {
            const [err, res] = await uni.request({
                url: 'https://api.example.com/news'
            });
            if (!err && res.data) {
                this.list = res.data;
            }
            this.loading = false;
        }
    }
}
</script>

这里用 v-if="loading" 控制骨架屏的显示,用 v-for 生成5个骨架卡片(模拟一屏的数量)。当数据返回后,loading 置为 false,真实列表立刻替换骨架。因为骨架的布局和真实卡片几乎一样,切换时页面不会抖动。

五、图片懒加载的几种实现方式

Uniapp的 image 组件自带一个 lazy-load 属性,设为true 后,在小程序端会自动启用懒加载,只在图片接近可视区域时才开始加载。这个属性在微信、支付宝、百度等小程序中都有效,在H5端则需要配合Intersection Observer自行处理。

如果 lazy-load 无法满足需求(比如想自定义加载占位图或控制加载时机),可以手写一个懒加载逻辑。核心是利用 uni.createIntersectionObserver 或者原生的 IntersectionObserver 来监听图片是否进入视口。我们选择跨端更友好的 uni.createIntersectionObserver,它在微信小程序和App-Vue中都能用。

下面展示一个可复用的懒加载指令(基于Vue的自定义指令):

// directives/lazy-image.js
export default {
    mounted(el, binding) {
        const src = binding.value;
        // 设置默认占位图
        el.src = '/static/placeholder.png';
        const observer = uni.createIntersectionObserver(this);
        observer.relativeToViewport({ bottom: 100 }); // 提前100px开始加载
        observer.observe(el, (res) => {
            if (res.intersectionRatio > 0) {
                el.src = src;
                observer.disconnect();
            }
        });
    }
}

使用时在模板里给 image 标签加上 v-lazy-image="item.thumb" 即可。不过需要注意的是,自定义指令在非Vue环境(如App-NVue)中可能不支持,这时还是用 lazy-load 属性最稳妥。

六、解决图片加载带来的高度跳动

即使有了懒加载,当图片最终加载完成时,如果它的高度未知,仍可能挤开后面的内容。解决办法是在图片外包一层固定高度的容器,或者给 image 设置 mode=”aspectFill” 并指定宽高。在新闻列表中,我们给每个 .news-img 设置 width: 100rpx; height: 100rpx,就保证了无论图片是否加载,卡片高度都固定。

另一个细节:在图片加载前显示一张灰色占位图或使用骨架块。上面的懒加载指令中我们先把 src 指向一张本地的 placeholder 图片,它会被立即渲染,而无需等待网络。这样用户体验会更连贯。

七、组合起来的效果检验

把骨架屏和图片懒加载结合起来后,用户进入列表页看到的流程是这样的:首先页面立刻出现5个闪烁的骨架卡片,没有白屏;骨架之下,网络请求在后台进行。数据返回后,骨架消失,真实卡片瞬间呈现。此时每张卡片的图片区域一开始是占位图,当用户往下滑动,快到某张图片的位置时,真实图片才开始加载,无缝替换掉占位图。整个过程没有突然的跳动,也没有多余的服务器请求。

如果你想在开发工具里验证懒加载是否生效,可以打开控制台的Network面板,筛选图片请求,然后慢慢滚动页面。你会发现只有滚动到接近底部时,新的图片请求才会发出,而不是一开始就全部请求。

八、可能遇到的坑和注意事项

  • 同一页面多个骨架屏列表:如果你在一个页面内有多个不同样式的列表,可以为每种卡片设计对应的骨架屏组件,千万不要试图用一个通用骨架去覆盖所有场景,否则对不齐的布局会让抖动更明显。
  • 骨架屏的数量不宜过多:一般预渲染首屏可见的数量即可,比如5条。如果数据量很大,用户滑到下面时骨架屏已经替换为真实内容了,不需要再出现。
  • 图片懒加载和骨架屏的拍照冲突:骨架屏里通常没有真实图片,所以不用懒加载指令。只在真实内容中启用懒加载。
  • H5端的IntersectionObserver:在H5中,uni.createIntersectionObserver 会降级使用原生的 IntersectionObserver,但需要注意 polyfill 问题(现代浏览器都已支持)。
  • 性能:骨架屏动画不宜过于复杂,简单的 shimmer 就足够,避免使用 box-shadow 或大量 transform 卡顿低端机型。

九、总结

骨架屏和图片懒加载都不是什么新奇的技术,但把它们在Uniapp里用组件化的思路重新封装一遍,能极大提升项目的可维护性。一旦你有了 SkeletonCardlazy-image 指令这两个小工具,以后任何列表页面都能在几分钟内添加“丝般顺滑”的加载体验,而不是每次都要重新写一套占位逻辑。

这套方案的核心就三点:用v-if配合请求状态控制骨架显示,用固定的宽高消除图片加载抖动,用交叉观察器把图片请求推迟到真正需要的时候。如果你正在为一个首屏加载慢的小程序头疼,不妨动手试试这两招。

Uniapp 组件级骨架屏与图片懒加载实战:提升首屏加载体验
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp Uniapp 组件级骨架屏与图片懒加载实战:提升首屏加载体验 https://www.taomawang.com/web/uniapp/2150.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务