VbeSkeleton.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import React, { memo, useMemo } from 'react';
  2. import { StyleSheet, View } from 'react-native';
  3. import Animated, {
  4. useSharedValue,
  5. withRepeat,
  6. withTiming,
  7. useAnimatedReaction,
  8. useDerivedValue,
  9. useAnimatedStyle,
  10. Easing,
  11. } from 'react-native-reanimated';
  12. import { useLayout, useGetBones } from 'react-native-reanimated-skeleton/lib/hooks';
  13. import TextView from './TextView';
  14. export const ANIMATION_TYPE = {
  15. NONE: "none",
  16. SHIVER: "shiver",
  17. PULSE: "pulse"
  18. }
  19. export const ANIMATION_DIRECTION = {
  20. HORIZONTAL_LEFT: "horizontalLeft",
  21. HORIZONTAL_RIGHT: "horizontalRight",
  22. VERTICAL_TOP: "verticalTop",
  23. VERTICAL_DOWN: "verticalDown",
  24. DIAGONAL_DOWN_LEFT: "diagonalDownLeft",
  25. DIAGONAL_DOWN_RIGHT: "diagonalDownRight",
  26. DIAGONAL_TOP_LEFT: "diagonalTopLeft",
  27. DIAGONAL_TOP_RIGHT: "diagonalTopRight"
  28. }
  29. const VbeSkeleton = ({
  30. style=styles.container,
  31. viewStyle={},
  32. textStyle={},
  33. text,
  34. duration=1200,
  35. layout=[],
  36. numberOfLines,
  37. animationType=ANIMATION_TYPE.SHIVER,
  38. animationDirection=ANIMATION_DIRECTION.HORIZONTAL_LEFT,
  39. easing=Easing.bezier(0.5, 0, 0.25, 1),
  40. isLoading=true,
  41. boneColor='#eeeeee',
  42. highlightColor='#f6f6f6',
  43. hasFadeIn=false,
  44. children
  45. }) => {
  46. const animationValue = useSharedValue(0);
  47. const loadingValue = useSharedValue(0);
  48. const shiverValue = useSharedValue(animationType === 'shiver' ? 1 : 0);
  49. const [componentSize, onLayout] = useLayout();
  50. const generalStyles = useMemo(
  51. () => ({
  52. animationDirection,
  53. animationType,
  54. boneColor,
  55. animationValue,
  56. highlightColor,
  57. }),
  58. [
  59. animationDirection,
  60. animationType,
  61. animationValue,
  62. boneColor,
  63. highlightColor,
  64. ],
  65. );
  66. const getBones = useGetBones(componentSize);
  67. useAnimatedReaction(
  68. () => ({ isLoading, loadingValue }),
  69. () => {
  70. if (isLoading && loadingValue.value !== 1) {
  71. /* NOTE: Reset behaviour to ensure animation always starts from the beginning */
  72. animationValue.value = 0;
  73. animationValue.value =
  74. shiverValue.value === 1
  75. ? withRepeat(withTiming(1, { duration, easing }), -1, false)
  76. : withRepeat(
  77. withTiming(1, { duration: duration / 2, easing }),
  78. -1,
  79. true,
  80. );
  81. }
  82. },
  83. [isLoading, shiverValue],
  84. );
  85. const opacity = useDerivedValue(() => {
  86. if (!isLoading) {
  87. return withTiming(1, { duration: 250 });
  88. }
  89. return 0;
  90. });
  91. const animatedStyle = useAnimatedStyle(() => ({
  92. opacity: opacity.value,
  93. }));
  94. return (
  95. <View style={style} onLayout={onLayout}>
  96. {isLoading ? (
  97. getBones({
  98. bonesLayout: layout,
  99. children,
  100. generalStyles,
  101. })
  102. ) : (
  103. <Animated.View style={[viewStyle, hasFadeIn ? animatedStyle : {}]}>
  104. { text
  105. ? <TextView style={textStyle} numberOfLines={numberOfLines}>{text}</TextView>
  106. : children
  107. }
  108. </Animated.View>
  109. )}
  110. </View>
  111. );
  112. };
  113. export default VbeSkeleton;
  114. const styles = StyleSheet.create({
  115. container: {
  116. flex: 1,
  117. alignItems: 'center',
  118. justifyContent: 'center'
  119. },
  120. viewStyle: {
  121. alignItems: 'center'
  122. }
  123. });