廓形仪rn版本-技术调研
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
3.2 KiB

  1. import React, { forwardRef } from 'react';
  2. import type { VariantProps } from '@gluestack-ui/nativewind-utils';
  3. import { Animated, Easing, Platform, View } from 'react-native';
  4. import { skeletonStyle, skeletonTextStyle } from './styles';
  5. type ISkeletonProps = React.ComponentProps<typeof View> &
  6. VariantProps<typeof skeletonStyle> & {
  7. isLoaded?: boolean;
  8. startColor?: string;
  9. };
  10. type ISkeletonTextProps = React.ComponentProps<typeof View> &
  11. VariantProps<typeof skeletonTextStyle> & {
  12. _lines?: number;
  13. isLoaded?: boolean;
  14. startColor?: string;
  15. };
  16. const Skeleton = forwardRef<
  17. React.ComponentRef<typeof Animated.View>,
  18. ISkeletonProps
  19. >(function Skeleton(
  20. {
  21. className,
  22. variant,
  23. children,
  24. startColor = 'bg-background-200',
  25. isLoaded = false,
  26. speed = 2,
  27. ...props
  28. },
  29. ref
  30. ) {
  31. const pulseAnim = new Animated.Value(1);
  32. const customTimingFunction = Easing.bezier(0.4, 0, 0.6, 1);
  33. const fadeDuration = 0.6;
  34. const animationDuration = (fadeDuration * 10000) / speed; // Convert seconds to milliseconds
  35. const pulse = Animated.sequence([
  36. Animated.timing(pulseAnim, {
  37. toValue: 1, // Start with opacity 1
  38. duration: animationDuration / 2, // Third of the animation duration
  39. easing: customTimingFunction,
  40. useNativeDriver: Platform.OS !== 'web',
  41. }),
  42. Animated.timing(pulseAnim, {
  43. toValue: 0.75,
  44. duration: animationDuration / 2, // Third of the animation duration
  45. easing: customTimingFunction,
  46. useNativeDriver: Platform.OS !== 'web',
  47. }),
  48. Animated.timing(pulseAnim, {
  49. toValue: 1,
  50. duration: animationDuration / 2, // Third of the animation duration
  51. easing: customTimingFunction,
  52. useNativeDriver: Platform.OS !== 'web',
  53. }),
  54. ]);
  55. if (!isLoaded) {
  56. Animated.loop(pulse).start();
  57. return (
  58. <Animated.View
  59. style={{ opacity: pulseAnim }}
  60. className={`${startColor} ${skeletonStyle({
  61. variant,
  62. class: className,
  63. })}`}
  64. {...props}
  65. ref={ref}
  66. />
  67. );
  68. } else {
  69. Animated.loop(pulse).stop();
  70. return children;
  71. }
  72. });
  73. const SkeletonText = forwardRef<
  74. React.ComponentRef<typeof View>,
  75. ISkeletonTextProps
  76. >(function SkeletonText(
  77. {
  78. className,
  79. _lines,
  80. isLoaded = false,
  81. startColor = 'bg-background-200',
  82. gap = 2,
  83. children,
  84. ...props
  85. },
  86. ref
  87. ) {
  88. if (!isLoaded) {
  89. if (_lines) {
  90. return (
  91. <View
  92. className={`${skeletonTextStyle({
  93. gap,
  94. })}`}
  95. ref={ref}
  96. >
  97. {Array.from({ length: _lines }).map((_, index) => (
  98. <Skeleton
  99. key={index}
  100. className={`${startColor} ${skeletonTextStyle({
  101. class: className,
  102. })}`}
  103. {...props}
  104. />
  105. ))}
  106. </View>
  107. );
  108. } else {
  109. return (
  110. <Skeleton
  111. className={`${startColor} ${skeletonTextStyle({
  112. class: className,
  113. })}`}
  114. {...props}
  115. ref={ref}
  116. />
  117. );
  118. }
  119. } else {
  120. return children;
  121. }
  122. });
  123. Skeleton.displayName = 'Skeleton';
  124. SkeletonText.displayName = 'SkeletonText';
  125. export { Skeleton, SkeletonText };