Более реалистичная анимация переворачивания карт.
В Интернете много карточек, и многие из них переворачиваются, открывая двусторонний контент. Но то, как многие из них переворачиваются, не... анатомически правильно.
На старых уроках письма мне советовали показывать, а не рассказывать: какая из этих карточек кажется вам лучше?
Первая карта, Пичу, делает обычный поворот на 180 градусов. Вторая карта, Райчу, поднимается со страницы и поворачивается на 180 градусов.
Карта Пичу — это то, как практически каждый урок рассказывает вам, как создать анимацию переворачивания карты. Дело в том, что в реальной жизни карты так не работают...
Карта Райчу реализует две тонкие детали:
- Реальную карту нужно поднять, чтобы ее перевернуть.
- Реальная карта имеет толщину.
Тонкость — это разница между чем-то приятным и чем-то, что приносит удовлетворение.
Уровень -1: Читерство с компонентом
Якорь для уровня 1: мошенничество с компонентом
Изначально я собирался просто написать о том, как сделать крутую переворачивающуюся анимацию, но немного увлекся и в итоге создал полностью повторно используемый компонент. Упс 🙂
Установите @auroratide/flip-card, и вы получите веб-компонент, который можно использовать в любом фреймворке, будь то React, Svelte, Vue или vanilla.
<flip-card>
<section slot="front">
<p>The front!</p>
</section>
<section slot="back">
<p>The back!</p>
</section>
</flip-card>
Что такое веб-компонент? Веб-компоненты создают новую семантику с помощью пользовательских элементов HTML. Если вы когда-нибудь говорили себе: «Черт, мне бы хотелось, чтобы это был обычный HTML-элемент», то веб-компоненты позволят вам сделать именно это: сделать его таковым! Узнайте больше о веб-компонентах и пошаговое руководство по созданию пользовательского элемента
Уровень 0: Основная карта
Базовая карточка, которую мы знаем и любим, основана на нескольких ключевых функциях CSS:
- 3D-преобразования — в частности, RotateY и перспектива
На самом деле это просто две коробки одинакового размера, расположенные друг над другом, одна из которых перевернута на 180 градусов, а ее задняя сторона невидима. Затем поверните их контейнер, чтобы увидеть содержимое второго ящика.
В множестве руководств уже подробно описано, как это работает, поэтому я просто свяжу всю соответствующую документацию и добавлю сюда немного аннотированного кода. Это послужит основой для восхождения по Лестнице Просветления при переворачивании карт.
HTML
<article class="perspective-container">
<div class="card">
<section class="front face" aria-hidden="false"></section>
<section class="back face" aria-hidden="true"></section>
</div>
</article>
CSS
.perspective-container {
perspective: 100em; /* creates an illusion of depth */
perspective-origin: center;
}
.card {
width: 15em;
aspect-ratio: 5 / 7;
position: relative;
transform-style: preserve-3d;
}
.card .face {
/* Hide the backside of the element, for when it is rotated */
backface-visibility: hidden;
}
.card .back {
/* The front and back elements occupy the same space */
position: absolute;
inset: 0;
transform: rotateY(180deg);
}
.card section {
width: 100%;
height: 100%;
}
/* Later sections will add code to actually flip the card */
/* We'll be applying transformations to the .card class mainly */
Уровень 1: Вертикальность
Когда вы переворачиваете реальную карту, вам нужно сначала поднять ее над поверхностью, иначе карта сольется со столом. Или, что более реалистично, карта гнется, проклиная вас на 7 лет.
Традиционный подход использует свойство CSS transition
, но все, что оно может сделать, это плавно перевести вас из одного состояния (rotateY(0deg)
) в другое состояние (rotateY(180deg)
). В случае поднятия карты начальное и конечное состояния одинаковы. Нам нужно среднее состояние, в котором карточка поднимается вертикально, поэтому нам нужен более мощный инструмент CSS.
Давайте использовать @keyframes и анимацию!
Аннотированный код!
CSS
@keyframes flip-to-front {
0% { transform: translateZ(0em) rotateY(-180deg); }
50% { transform: translateZ(var(--flip-height)) rotateY(-270deg); }
100% { transform: translateZ(0em) rotateY(-360deg); }
}
/* I'm using a second animation for two reasons:
* 1. It allows the card to always flip in one direction.
* 2. The renderer only plays an animation if it changes.
*/
@keyframes flip-to-back {
0% { transform: translateZ(0em) rotateY(0deg); }
50% { transform: translateZ(var(--flip-height)) rotateY(-90deg); }
100% { transform: translateZ(0em) rotateY(-180deg); }
}
.card {
--flip-height: 17.5em;
animation-duration: 0.75s;
animation-fill-mode: both;
animation-timing-function: linear;
/* NOTE: We're NOT setting the animation with CSS */
/* By using Javascript, it's easier to prevent the animation from playing as soon as the page loads. */
}
Javascript
function flipCard(card) {
card.classList.toggle("facedown")
const isFacedown = card.classList.contains("facedown")
card.style.animationName = isFacedown
? "flip-to-back"
: "flip-to-front"
}
Уровень 2: Толщина
Реальные карты имеют небольшую, хотя и не нулевую толщину. А с помощью 3D CSS мы также можем придать толщину нашим виртуальным картам! Эффект незаметен, но делает вращение более физическим.
Стратегия здесь состоит в том, чтобы собрать четыре пустых блока div
, представляющих края карты. Мы сделаем их такими же широкими/высотными, как толщина карточки, расположим их вдоль границ карточки, а затем повернём на странице.
Аннотированный код!
HTML
<div class="card">
<section class="front face" aria-hidden="false"></section>
<section class="back face" aria-hidden="true"></section>
<!-- NEW! We need divs that represent the 4 edges -->
<div class="top edge"></div>
<div class="right edge"></div>
<div class="bottom edge"></div>
<div class="left edge"></div>
</div>
CSS
.card {
--card-depth: 0.25em;
/* Without special corner logic, a card with thickness cannot have border radius */
border-radius: 0;
}
.card .back {
/* Push the back of the card backward to give space for the edges to live */
transform: translateZ(calc(-1 * var(--card-depth))) rotateY(180deg);
}
.edge {
position: absolute;
background-color: black;
}
/* All of this code is aligning the edges, rotating them into the page */
.right, .left {
width: var(--card-depth);
height: 100%;
inset-block: 0;
} .right {
inset-inline-end: 0;
transform: rotateY(270deg);
transform-origin: right center;
} .left {
inset-inline-start: 0;
transform: rotateY(90deg);
transform-origin: left center;
}
.top, .bottom {
width: 100%;
height: var(--card-depth);
inset-inline: 0;
} .top {
inset-block-start: 0;
transform: rotateX(270deg);
transform-origin: center top;
} .bottom {
inset-block-end: 0;
transform: rotateX(90deg);
transform-origin: center bottom;
}
Уровень 3: Круглые углы + толщина
Якорь для уровня 3: круглые углы + толщина
Вы когда-нибудь пробовали создать вращающийся цилиндр с помощью CSS? Оказывается, вы не можете этого сделать, потому что в CSS нет изогнутых трехмерных поверхностей. Самый разумный способ — смоделировать цилиндр с множеством тонких плоских поверхностей.
Как только наша карта набирает толщину, любые закругленные углы внезапно становятся четвертьцилиндрами. Поэтому нам нужна продвинутая магическая математика, чтобы они выглядели правильно!
Стратегия состоит в том, чтобы имитировать каждый закругленный угол как серию маленьких плоских элементов, расположенных в четверти круга, радиусы которых равны радиусу границы карты. Количество используемых нами элементов div
— это то, что я называю --corner-granularity
. Более высокая степень детализации углов означает более гладкий угол, но используется больше элементов div
.
HTML
<div class="card">
<!-- ...front, back, sides... -->
<div class="top-right corner">
<div style="--i: 0;"></div>
<div style="--i: 1;"></div>
<div style="--i: 2;"></div>
</div>
<div class="bottom-right corner">
<div style="--i: 0;"></div>
<div style="--i: 1;"></div>
<div style="--i: 2;"></div>
</div>
<div class="bottom-left corner">
<!-- ... -->
</div>
<div class="top-left corner">
<!-- ... -->
</div>
</div>
CSS
.card {
/* The number of faces used to simulate a round corner. More faces means more smooth. */
--corner-granularity: 3;
--border-radius: 1.5em;
border-radius: var(--border-radius);
}
.corner > * {
background-color: black;
}
/* We have to override the edges so they do not overlap the corners */
.right, .left {
inset-block: var(--border-radius);
height: calc(100% - 2 * var(--border-radius));
} .top, .bottom {
inset-inline: var(--border-radius);
width: calc(100% - 2 * var(--border-radius));
}
.corner {
--n: var(--corner-granularity);
--r: var(--border-radius);
position: absolute;
transform-style: preserve-3d;
}
/* A corner is composed of a finite number of flat faces that, when arranged in just the right way, looks rounded. */
/* We need to do it this way because curved 3D surfaces do not exist in CSS. */
.corner > * {
position: absolute;
inset-block-end: 0;
width: var(--card-depth);
height: calc(2 * var(--r) * sin(45deg / var(--n)));
transform-origin: bottom center;
/* This math constructs a single corner. */
/* I derived it on a paper somewhere and threw it away, */
/* so you'll have to derive it yourself if you want to understand what's happening (: */
transform:
translateZ(calc(var(--r) * cos(var(--i) * 90deg / var(--n))))
translateY(calc(-1 * var(--r) * sin(var(--i) * 90deg / var(--n))))
rotateX(calc(45deg * (2 * var(--i) + 1) / var(--n)));
}
/* The rest of this code slots the corners where they belong. */
.top-right {
inset-block-start: 0;
inset-inline-end: 0;
transform:
rotateY(90deg)
translateZ(calc(-1 * var(--r)))
translateY(var(--r));
} .bottom-right {
inset-block-end: 0;
inset-inline-end: 0;
transform:
rotateY(90deg)
rotateX(270deg)
translateZ(calc(-1 * var(--r)))
translateY(var(--r));
} .bottom-left {
inset-block-end: 0;
inset-inline-start: 0;
transform:
rotateY(90deg)
rotateX(180deg)
translateZ(calc(-1 * var(--r)))
translateY(var(--r));
} .top-left {
inset-block-start: 0;
inset-inline-start: 0;
transform:
rotateY(90deg)
rotateX(90deg)
translateZ(calc(-1 * var(--r)))
translateY(var(--r));
}
Уровень 🧠: Помните о доступности!
Доступность — это практика учета всех людей, которые могут использовать ваш веб-сайт, и обеспечения возможности его использования как можно большим количеством из них. Флиппи-карты могут создать несколько ловушек, если мы не будем осторожны!
- Что делать, если человек не может пользоваться мышью? Является ли наведение курсора единственным способом перевернуть карту?
- Что, если человек использует клавишу
Tab
для навигации? Наткнутся ли они на кнопку, спрятанную на обратной стороне вашей карты?
- Что, если человек использует программу чтения с экрана, чтобы прочитать содержимое страницы вслух? Будет ли он читать содержимое, спрятанное на обратной стороне карты?
- Что делать, если человек предпочитает меньше анимации? Будет ли их раздражать анимация переворачивания карты?
Хотя целью этой статьи не является изучение доступности, тем не менее, это важные вопросы, которые следует рассмотреть. Вот несколько инструментов, которые вы можете использовать для их решения.
Заключение
Хорошо, я признаю, что на самом деле нет ничего плохого в обычном перевороте карты и уроках, которые этому учат ❤️ Я имею в виду, почему он не может представлять собой переворот карты, происходящий в воздухе?
Я просто хотел поделиться чем-то, что я попробовал и мне понравилось, и если вам это нравится, не стесняйтесь использовать это тоже!