Issue
I want to recreate this Underline Effect from this Codepen with React and Typescript
The Codepen: https://codepen.io/krakruhahah/pen/jOzwXww
I think my issue in the code down below is the interface, I started to declare my types but still it does not recognize them. It says they are any. But max is declared as number but shows up as any still. I am unsure why. The functions are described as comments.
tsx:
import React from 'react';
import Typography from '@mui/material/Typography';
import { Box } from '@mui/material';
interface Props {
max: number;
}
const styles = {
body: {
width: "80%",
margin: "10vw auto",
},
heading: {
fontFamily: "Playfair Display, serif",
fontSize: "10vw",
},
"subheading": {
fontFamily: "Open Sans, sans-serif",
fontSize: "1em",
lineHeight: "1.5",
},
"@media screen and (min-width: 40em)": {
body: {
width: "50%",
},
heading:{
fontSize: "6vw",
},
subheading: {
fontSize: "1.75vw",
}
},
"underline--magical": {
backgroundImage: "linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%)",
backgroundRepeat: "no-repeat",
backgroundSize: "100% 0.2em",
backgroundPosition: "0 88%",
transition: "backgroundSize 0.25s ease-in",
"&:hover": {
backgroundSize: "100% 88%",
},
},
};
function Effect(props: Props) {
// VARIABLES
const magicalUnderlines = Array.from(document.querySelectorAll('.underline--magical'));
const gradientAPI = 'https://gist.githubusercontent.com/wking-io/3e116c0e5675c8bcad8b5a6dc6ca5344/raw/4e783ce3ad0bcd98811c6531e40256b8feeb8fc8/gradient.json';
// HELPER FUNCTIONS
// 1. Get random number in range. Used to get random index from array.
const randNumInRange = max => Math.floor(Math.random() * (max - 1));
// 2. Merge two separate array values at the same index to
// be the same value in new array.
const mergeArrays = (arrOne, arrTwo) => arrOne
.map((item, i) => `${item} ${arrTwo[i]}`)
.join(', ');
// 3. Curried function to add a background to array of elms
const addBackground = (elms) => (color) => {
elms.forEach(el => {
el.style.backgroundImage = color;
});
}
// 4. Function to get data from API
const getData = async(url): Promise<XMLHttpRequest> => {
const response = await fetch(url);
const data = await response.json();
return data.data;
}
// 5. Partial Application of addBackground to always apply
// background to the magicalUnderlines constant
const addBackgroundToUnderlines = addBackground(magicalUnderlines);
// GRADIENT FUNCTIONS
// 1. Build CSS formatted linear-gradient from API data
const buildGradient = (obj) => `linear-gradient(${obj.direction}, ${mergeArrays(obj.colors, obj.positions)})`;
// 2. Get single gradient from data pulled in array and
// apply single gradient to a callback function
const applyGradient = async(url, callback): Promise<XMLHttpRequest> => {
const data = await getData(url);
const gradient = buildGradient(data[randNumInRange(data.length)]);
callback(gradient);
}
// RESULT
applyGradient(gradientAPI, addBackgroundToUnderlines);
return (
<Box>
<Typography sx={styles.heading}>
Look At This <span style={styles['underline--magical']}>Pretty</span> Underline
</Typography>
<Typography sx={styles.subheading}>
Wow this one is super incredibly cool, and this{' '}
<span style={styles['underline--magical']}>one is on Multiple Lines!</span> I wish I had found this like thirty
projects ago when I was representing the lollipop guild.
</Typography>
</Box>
);
}
export { Effect };
Solution
To generate a random gradient you have two arrays colors and position, but for
linear-gradientwe need a string of tuples. I created a helper function generateGradientRangeArray to get this string.Since we using React, we can create a hook (useGradient) to fetch data from outside and transform it right here.
Using the @emotion and Material UI components, we add additional propperties to Box and Typography
And finally we create the Underline component, check the gradient property, and pass props to the style.
App.tsx
import styled from "@emotion/styled";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { Underline } from "./Underline";
import useGradient from "./useGradient";
const Heading = styled(Typography)`
font-family: "Playfair Display", serif;
font-size: 10vw;
@media screen and (min-width: 40em) {
font-size: 6vw;
}
`;
const Subheading = styled(Typography)`
font-family: "Open Sans", sans-serif;
font-size: 1em;
@media screen and (min-width: 40em) {
font-size: 1.75vw;
}
`;
export default function App() {
const gradient = useGradient();
return (
<div className="App">
<Box>
<Heading>
Look At This <Underline gradient={gradient}>Pretty</Underline>{" "}
Underline
</Heading>
<Subheading>
Wow this one is super incredibly cool, and this{" "}
<Underline gradient={gradient}>one is on Multiple Lines!</Underline> I
wish I had found this like thirty projects ago when I was representing
the lollipop guild.
</Subheading>
</Box>
</div>
);
}
useGradient.ts
import { useEffect, useState } from "react";
import { generateGradientRangeArray, randNumInRange } from "./Helpers";
import { GradientType, IResult } from "./types";
const GRADIENT_API =
"https://gist.githubusercontent.com/wking-io/3e116c0e5675c8bcad8b5a6dc6ca5344/raw/4e783ce3ad0bcd98811c6531e40256b8feeb8fc8/gradient.json";
const useGradient = () => {
const [gradients, setGradients] = useState<GradientType>();
useEffect(() => {
const getData = async (url: string): Promise<void> => {
// get the data from the api
const response = await fetch(url);
const result = (await response.json()) as IResult;
// Get single gradient from data pulled in array
const gradient = result.data[randNumInRange(result.data.length)];
const transform = generateGradientRangeArray(gradient);
// set state with the result
setGradients(transform);
};
// Catching errors
getData(GRADIENT_API).catch(console.error);
}, []);
return gradients;
};
export default useGradient;
Underline.tsx
import styled from "@emotion/styled";
import { Props, GradientType } from "./types";
const UnderlineStyle = styled.span<{ gradient: GradientType }>`
background-image: linear-gradient(
${(props) => props.gradient.direction},
${(props) => props.gradient.range}
);
background-repeat: no-repeat;
background-size: 100% 0.2em;
background-position: 0 88%;
transition: background-size 0.25s ease-in;
&:hover {
background-size: 100% 88%;
}
`;
export const Underline = ({ children, gradient }: Props) => {
if (!gradient) return null;
return <UnderlineStyle gradient={gradient}>{children}</UnderlineStyle>;
};
Helpers.ts
import { IGradient, GradientType } from "./types";
/// HELPER FUNCTIONS
// Get random number in range. Used to get random index from array.
export const randNumInRange = (max: number) =>
Math.floor(Math.random() * (max - 1));
// Create range of colors for gradient
export const generateGradientRangeArray = (data: IGradient): GradientType => {
const { colors, direction, name, positions } = data;
const rangeColorsArray: string[] = [];
for (const clr in colors) {
for (const pos in positions) {
if (clr === pos) {
rangeColorsArray.push(`${colors[clr]} ${positions[pos]},`);
}
}
}
const createGradientString = rangeColorsArray.join(" ").slice(0, -1);
return { name, direction, range: createGradientString };
};
types.ts
import { ReactNode } from "react";
export interface IGradient {
name: string;
direction: string;
colors: string[];
positions: string[];
}
export type GradientType = { range: string } & Pick<
IGradient,
"name" | "direction"
>;
export interface IResult {
data: IGradient[];
}
export interface Props {
children: ReactNode;
gradient?: GradientType;
}
Answered By - Anton
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.