Как сделать адаптивную карточку с эмодзи? (TS React и Chakra UI)
Экспериментируя с градиентными границами, я наткнулся на интересную технику - карточки, которые адаптируются к содержимому внутри них. Демонстрацию этого эффекта в действии можно посмотреть здесь.
Вот что мы будем создавать сегодня:
Давайте пройдемся по шагам с самого начала и разложим всё по полочкам!
Инициализация проекта
Мы будем строить с нуля, ничего не упрощая. Мы будем использовать следующие технологические стеки:
- Vite с React и TypeScript: Для быстрой настройки среды нашего проекта.
- Chakra UI: Это не обязательно для эффекта, но реквизит стиля Chakra облегчает процесс стилизации.
- Framer Motion: Это зависимость от Chakra UI, которая пригодится, если мы решим анимировать карту.
Настройте проект с помощью Vite: Начните с выполнения следующих действий:
npm create vite@latest
При появлении запроса выберите React with TypeScript.
Установите Chakra UI и Framer Motion:
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
Создайте тему Chakra: Сохраните в файле theme.ts
следующее:
import { ThemeConfig, extendTheme } from '@chakra-ui/react';
const config: ThemeConfig = {
initialColorMode: 'dark',
useSystemColorMode: false,
};
const theme = extendTheme({
config,
styles: {
global: () => ({
body: {
bg: '#121212',
},
}),
},
});
export default theme;
При этом цветовая схема Chakra устанавливается в темный режим и применяется темно-серый фон.
Интегрируйте провайдер Chakra: Измените файл src/main.tsx
, чтобы включить в него провайдер Chakra:
import { ChakraProvider } from '@chakra-ui/react';
import theme from './theme';
...
<React.StrictMode>
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>
</React.StrictMode>
Корректировки стиля: Чтобы страница занимала весь экран, установите значение min-height: 100vh;
для элемента #root
в файле App.css
.
Создание базовой карточки
Для начала создадим базовый компонент карточки.
Создайте компонент: Создайте компонент src/components/EmojiCard.tsx
:
import { Box, BoxProps } from '@chakra-ui/react';
interface Props {
emoji: string;
}
export default function EmojiCard({ emoji, children }: Props) {
return (
/* main container */
<Box position="relative" maxW={700} borderRadius={8} bg="#181818">
/* content wrapper */
<Box
px={8}
py={4}
gap={{ base: 0, sm: 8 }}
display="flex"
alignItems="center"
flexDirection={{ base: 'column', sm: 'row' }}
borderRadius={6}
>
/* emoji container */
<Box
display="flex"
alignItems="center"
justifyContent="center"
fontSize="80px"
width="200px"
>
{emoji}
</Box>
/* text content container */
<Box height="100%" textAlign={{ base: 'center', sm: 'left' }}>
{children}
</Box>
</Box>
</Box>
);
}
Box
- это компонент Chakra UI, который позволяет нам передавать реквизиты стиля напрямую.
В этой базовой конфигурации у нас есть окружающий основной контейнер с ограниченной шириной и фоном. Внутри также есть обертка содержимого, в которой есть место для наших эмодзи и текстового содержимого.
Этот код служит базовой структурой для нашей карточки.
Рендеринг компонента: Обновите App.tsx
для рендеринга нашего нового компонента:
import EmojiCard from "./components/EmojiCard";
...
<EmojiCard emoji="🍬">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi explicabo doloremque, accusantium repellat dolorem natus soluta quos!
</EmojiCard>
Эффект
Когда начальная настройка выполнена, приступаем к самому интересному - созданию эффекта!
Фоновый эмодзи: Добавьте эмодзи в качестве фона в основной контейнер, но перед оберткой содержимого:
<Box
position="absolute"
left={0}
top={0}
width="100%"
height="100%"
justifyContent={'center'}
alignItems={'center'}
display="flex"
_before={{
content: `"${emoji}"`,
fontSize: 80,
}}
/>;
Поскольку фоновый элемент имеет позицию absolute
, а наше содержимое по умолчанию имеет позицию static
, контекст укладки CSS помещает фон поверх содержимого. Чтобы исправить это, установите для обертки содержимого значение position="relative"
.
Изменения в стиле: Увеличьте размер эмодзи, установив размер шрифта до 1100, и добавьте фильтр к элементу фона эмодзи: filter={"blur(80px)"}
.
Это интересный градиентный эффект, но мы хотим ограничить его рамками нашей карточки. Добавьте в основной контейнер параметр overflow="hidden"
.
Теперь добавим к обертке содержимого следующее, чтобы вырезать место для содержимого в середине и получить тонкую границу с помощью этих градиентных цветов:
backgroundColor="#151515"
border="2px solid transparent"
backgroundClip="padding-box"
Интересным здесь является свойство backgroundClip
. Оно определяет, насколько далеко простирается фон внутри элемента.
padding-box
означает, что установленный нами backgroundColor
будет распространяться до внешнего края подложки, но не будет выходить за ее границы.
Теперь эта карта должна реагировать на все передаваемые в нее emoji и использовать их для заполнения градиента фона:
Добавление полировки
Давайте еще больше усилим этот эффект!
Градиентный фон: Заменим установленное нами свойство backgroundColor на следующее:
backgroundImage="linear-gradient(rgb(20 20 20 / 0.8), rgb(20 20 20))"
В результате сплошной фон будет заменен светлым градиентом, слегка прозрачным в верхней части, чтобы часть цвета фона могла просвечивать:
Анимация фона: Мы можем сделать карточку более динамичной и привлекательной, анимировав фон эмодзи с помощью Framer Motion:
import { motion, useTime, useTransform } from 'framer-motion';
const AnimatedBox = motion(Box);
...
// inside our component
const time = useTime();
const rotate = useTransform(
time,
[0, 16000], // every 16 seconds...
[0, 360], // ...rotate 360deg
{ clamp: false }, // repeat the animation
);
...
// convert our emoji background to a framer motion component
<AnimatedBox
// pass in our special framer motion value
style={{ rotate }}
...
Мы преобразуем компонент Box
для фона эмодзи в AnimatedBox
с помощью утилиты motion
от Framer Motion. Это позволяет компоненту принимать специальные значения, которые Framer Motion анимирует для нас вне стандартного цикла рендеринга React.
Мы используем хуки useTime
и useTransform
из Framer Motion, чтобы рассчитать, насколько сильно должен вращаться эмодзи. Затем мы передаем значение rotate
в качестве свойства стиля нашему компоненту Framer Motion для обработки анимации.
Это и есть конечный результат!
Молодцы, что дошли до этого! Окончательный вариант кода можно посмотреть на CodeSandbox, здесь, а оригинальная демонстрация эксперимента показывает его в действии с различными эмодзи.
Очень интересно натыкаться на подобные техники. Мы только поверхностно ознакомились с ними, а ведь существует множество различных направлений.
Представьте себе этот эффект с различными режимами освещения или в виде кнопки с пиктограммой, которая самостоятельно изменяет свой стиль в зависимости от предоставленной пиктограммы.
Не ограничивайтесь эмодзи: можно использовать любые изображения, узоры или цветной текст. Экспериментируйте с различными рамками. Поиграйте с размытием фильтра - уберите его или используйте другой.
Сообщите нам в комментариях, что у вас получилось!