Initial commit: expo-popcore-app
This commit is contained in:
140
components/GradientText.tsx
Normal file
140
components/GradientText.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { TextProps, StyleSheet, TextStyle, View, Text, ViewStyle } from 'react-native'
|
||||
import Svg, { Defs, LinearGradient as SvgLinearGradient, Stop, Text as SvgText } from 'react-native-svg'
|
||||
|
||||
interface GradientTextProps extends Omit<TextProps, 'style'> {
|
||||
colors: [string, string, ...string[]]
|
||||
start?: { x: number; y: number }
|
||||
end?: { x: number; y: number }
|
||||
style?: TextStyle
|
||||
}
|
||||
|
||||
/**
|
||||
* 渐变文字组件(使用 SVG 实现,无需原生模块)
|
||||
*
|
||||
* @example
|
||||
* <GradientText
|
||||
* colors={['#FF9966', '#FF6699', '#9966FF']}
|
||||
* start={{ x: 0, y: 0 }}
|
||||
* end={{ x: 1, y: 0 }}
|
||||
* style={{ fontSize: 24, fontWeight: 'bold' }}
|
||||
* >
|
||||
* 渐变文字
|
||||
* </GradientText>
|
||||
*/
|
||||
export default function GradientText({
|
||||
colors,
|
||||
start = { x: 0, y: 0 },
|
||||
end = { x: 1, y: 0 },
|
||||
style,
|
||||
children,
|
||||
...textProps
|
||||
}: GradientTextProps) {
|
||||
const gradientId = useMemo(() => `gradient-${Math.random().toString(36).substr(2, 9)}`, [])
|
||||
|
||||
// 从 style 中提取字体相关属性
|
||||
const fontSize = (style?.fontSize as number) || 14
|
||||
const fontWeight = style?.fontWeight || 'normal'
|
||||
const fontFamily = style?.fontFamily
|
||||
const textAlign = style?.textAlign || 'left'
|
||||
|
||||
// 使用一个隐藏的 Text 来测量文字尺寸
|
||||
const [textWidth, setTextWidth] = React.useState(0)
|
||||
const [textHeight, setTextHeight] = React.useState(fontSize * 1.2)
|
||||
|
||||
// 计算渐变坐标(转换为百分比)
|
||||
const x1 = `${start.x * 100}%`
|
||||
const y1 = `${start.y * 100}%`
|
||||
const x2 = `${end.x * 100}%`
|
||||
const y2 = `${end.y * 100}%`
|
||||
|
||||
// 计算文字位置
|
||||
const textX = textAlign === 'center' ? (textWidth / 2) : textAlign === 'right' ? textWidth : 0
|
||||
const textY = textHeight * 0.75 // 垂直位置,稍微调整以匹配基线
|
||||
|
||||
// 从 style 中提取 ViewStyle 兼容的属性
|
||||
const containerStyle: ViewStyle = {
|
||||
...(style?.margin !== undefined && { margin: style.margin }),
|
||||
...(style?.marginTop !== undefined && { marginTop: style.marginTop }),
|
||||
...(style?.marginBottom !== undefined && { marginBottom: style.marginBottom }),
|
||||
...(style?.marginLeft !== undefined && { marginLeft: style.marginLeft }),
|
||||
...(style?.marginRight !== undefined && { marginRight: style.marginRight }),
|
||||
...(style?.marginHorizontal !== undefined && { marginHorizontal: style.marginHorizontal }),
|
||||
...(style?.marginVertical !== undefined && { marginVertical: style.marginVertical }),
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, containerStyle]}>
|
||||
{/* 隐藏的 Text 用于测量尺寸和占据空间 */}
|
||||
<Text
|
||||
style={[
|
||||
styles.measureText,
|
||||
{
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
textAlign,
|
||||
},
|
||||
]}
|
||||
onLayout={(e) => {
|
||||
const { width, height } = e.nativeEvent.layout
|
||||
setTextWidth(width || 0)
|
||||
setTextHeight(Math.max(height || fontSize * 1.2, fontSize * 1.2))
|
||||
}}
|
||||
>
|
||||
{String(children)}
|
||||
</Text>
|
||||
|
||||
{/* SVG 渐变文字 */}
|
||||
{textWidth > 0 && textHeight > 0 && (
|
||||
<Svg
|
||||
width={textWidth}
|
||||
height={textHeight}
|
||||
style={styles.svgOverlay}
|
||||
>
|
||||
<Defs>
|
||||
<SvgLinearGradient id={gradientId} x1={x1} y1={y1} x2={x2} y2={y2}>
|
||||
{colors.map((color, index) => (
|
||||
<Stop
|
||||
key={index}
|
||||
offset={`${(index / (colors.length - 1)) * 100}%`}
|
||||
stopColor={color}
|
||||
/>
|
||||
))}
|
||||
</SvgLinearGradient>
|
||||
</Defs>
|
||||
<SvgText
|
||||
x={textX}
|
||||
y={textY}
|
||||
fontSize={fontSize}
|
||||
fontWeight={fontWeight}
|
||||
fontFamily={fontFamily}
|
||||
textAnchor={textAlign === 'center' ? 'middle' : textAlign === 'right' ? 'end' : 'start'}
|
||||
fill={`url(#${gradientId})`}
|
||||
>
|
||||
{String(children)}
|
||||
</SvgText>
|
||||
</Svg>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'transparent',
|
||||
position: 'relative',
|
||||
},
|
||||
measureText: {
|
||||
opacity: 0,
|
||||
includeFontPadding: false,
|
||||
// 确保文本占据空间
|
||||
minHeight: 1,
|
||||
},
|
||||
svgOverlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user