I'm trying to create a React component that renders a stroked text with a progressive fill color based on a prop that works with any font.
Because of a bug with -webkit-text-stroke
with certain type of fonts (see this), I went for a solution that overlays two spans on top of each other. One for the stroke and another for the background fill (based on this answer)
This is the component code I have so far:
type StrokeFillWipeTextProps = {
text: string;
color: string;
wipeColor: string;
progress: number;
className: string;
};
export const StrokeFillWipeText = ({
text,
color,
wipeColor,
progress,
className,
}: StrokeFillWipeTextProps) => {
const wrapperStyle: React.CSSProperties = {
position: 'relative',
WebkitTextStroke: '10px black',
};
const innerStyle: React.CSSProperties = {
position: 'absolute',
left: 0,
pointerEvents: 'none',
backgroundClip: 'text',
backgroundSize: '200% 100%',
transition: 'background-position',
WebkitTextStroke: 0,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
whiteSpace: 'pre',
color: color,
backgroundPositionX: `${100 - progress}%`,
backgroundImage: `linear-gradient(to right, ${wipeColor}, ${wipeColor} 50%, ${color} 50%)`,
};
return (
<div className={className}>
<span style={wrapperStyle}>
{text}
<span style={innerStyle} aria-hidden>
{text}
</span>
</span>
</div>
);
};
So when progress is 0
the font color should be color
. When progress is 50
, the first half of the text should be colored with wipeColor
and the second half with color
and so on.
Here's a screenshot of this component rendered three times with progress 10
, 50
and 90
respectively.
As you can see, with certain fonts and letters the first letter (also the last) is cropped. The actual text is overflowing the wrapper span
as shown here:
Here's a repro StackBlitz:
https://stackblitz.com/edit/vitejs-vite-tjgpxk?file=src%2FStrokeFillWipeText.tsx
How can I get this to work with all font families, characters and font sizes?
Also, I need the progress
prop to be exact. 0 meaning no wipe fill at all, 100 meaning full wipe fill and all in betweens.