【React Native】渐变骨架屏组件SkeletonElement

React Native 渐变骨架屏组件 SkeletonElement 使用指南

一、前言

在移动端开发中,数据加载过程中的空白等待会显著降低用户体验。骨架屏(Skeleton Screen) 是一种广受推崇的加载策略:它通过渲染与真实内容结构一致的灰色占位块,在数据返回前给予用户明确的视觉反馈。

本文提供一个轻量、高性能、支持自定义样式的 SkeletonElement 组件,适用于任何需要占位展示的 UI 元素。


二、组件特性

  • 完全自定义样式:通过 style 属性控制尺寸、方向、间距等
  • 流畅渐变动画:使用 Animated 实现原生驱动的高帧率闪烁效果
  • 轻量无依赖:仅基于 React Native 内置模块,零第三方依赖
  • 灵活配色:支持自定义背景色和高亮色
  • 自动清理:组件卸载时自动停止动画,防止内存泄漏

三、快速上手

<View>
  {/* 标题骨架 */}
  <SkeletonElement style={{ height: 24, width: '80%', marginBottom: 16 }} />
  
  {/* 多行文本骨架 */}
  <SkeletonElement style={{ height: 16, width: '100%', marginBottom: 8 }} />
  <SkeletonElement style={{ height: 16, width: '90%', marginBottom: 8 }} />
  <SkeletonElement style={{ height: 16, width: '95%' }} />
  
  {/* 按钮骨架 */}
  <SkeletonElement 
    style={{ 
      height: 48, 
      width: '50%', 
      borderRadius: 24, 
      alignSelf: 'center',
      marginTop: 20 
    }} 
  />
</View>

四、API 说明

Props

属性类型默认值说明
styleStyleProp<ViewStyle>undefined自定义骨架样式(宽高、方向、圆角等)
backgroundColorstring#E4E4E4骨架背景色
highlightColorstring#D0D0D0高亮闪烁色(较背景色稍亮)
speednumber1600完整闪烁周期(毫秒),值越小动画越快

五、源码解析

核心逻辑

  1. 动画驱动:使用 useRef(new Animated.Value(0.4)) 创建不触发重渲染的动画值
  2. 循环闪烁:通过 Animated.loop + Animated.sequence 实现“暗→亮→暗”的循环
  3. 性能优化:启用 useNativeDriver: true,动画在原生线程运行
  4. 资源清理useEffect 返回清理函数,确保组件卸载时停止动画

关键代码片段

const opacityValue = useRef(new Animated.Value(0.4)).current;

useEffect(() => {
  Animated.loop(
    Animated.sequence([
      Animated.timing(opacityValue, { toValue: 1, ... }),
      Animated.timing(opacityValue, { toValue: 0.4, ... })
    ])
  ).start();

  return () => opacityValue.stopAnimation();
}, [opacityValue, speed]);

六、完整源码

import React, {useEffect, useRef} from 'react';
import {Animated, Easing, StyleProp, View, ViewStyle} from 'react-native';

interface SkeletonElementProps {
  style?: StyleProp<ViewStyle>;
  backgroundColor?: string;
  highlightColor?: string;
  speed?: number;
}

export const SkeletonElement: React.FC<SkeletonElementProps> = ({
  style,
  backgroundColor = '#E4E4E4',
  highlightColor = '#D0D0D0',
  speed = 1600,
}) => {
  const useSkeletonAnimation = (speed: number) => {
    const opacityValue = useRef(new Animated.Value(0.4)).current;

    useEffect(() => {
      Animated.loop(
        Animated.sequence([
          Animated.timing(opacityValue, {
            toValue: 1,
            duration: speed / 2,
            easing: Easing.inOut(Easing.ease),
            useNativeDriver: true,
          }),
          Animated.timing(opacityValue, {
            toValue: 0.4,
            duration: speed / 2,
            easing: Easing.inOut(Easing.ease),
            useNativeDriver: true,
          }),
        ]),
      ).start();

      // 返回清理函数
      return () => {
        opacityValue.stopAnimation();
      };
    }, [opacityValue, speed]);

    return opacityValue;
  };

  const opacityValue = useSkeletonAnimation(speed);

  // 合并样式
  const skeletonStyle: ViewStyle = {
    backgroundColor,
    overflow: 'hidden',
    position: 'relative',
    ...(style as ViewStyle),
  };

  // 添加渐变动画效果
  const highlightOverlay = (
    <Animated.View
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: highlightColor,
        opacity: opacityValue,
      }}
    />
  );

  return <View style={skeletonStyle}>{highlightOverlay}</View>;
};

💡 提示:将此组件与条件渲染结合,即可实现优雅的数据加载过渡:

{loading ? <SkeletonElement style={{ height: 200 }} /> : <RealContent />}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值