В этой статье в основном обсуждается реализация следующих двух анимаций флипбука:
Во-первых, это эффект полного перелистывания страницы:
Эффект этого полного переворота страницы в основном заключается в выполнении анимации rotateY в сочетании с некоторыми свойствами CSS 3D для достижения.
Эффект второго отражения полилинии показан на следующем рисунке:
В основном путем вычисления положения, в котором страница переворачивается.
Эти два принципа не очень сложны, и каждая деталь должна быть хорошо скоординирована, чтобы сформировать связную анимацию флип-книги.
Давайте сосредоточимся на реализации первого эффекта перелистывания страниц.
1. Базовая компоновка
Эта реализация относительно проста, мы сначала подготовим структуру DOM, как показано в следующем коде:
<ul class="pages">
<!--一个li.paper包含了正反两页-->
<li class="paper" data-left>
<!--一个.page就是一页内容-->
<div class="page page-1-back">
<img src="1.jpg" alt>
</div>
<div class="page page-1">
<img src="2.jpg" alt>
</div>
</li>
<li class="paper" data-right>
<div class="page page-2">
<img src="3.jpg" alt>
</div>
<div class="page page-2-back">
<img src="4.jpg" alt>
</div>
</li>
<!--其它页内容省略-->
</ul>
Одинli.paper
Это означает лист бумаги, включающий две страницы, лицевую и обратную. абсолютное позиционирование. Так что если это следующая страница, data-right должен быть анимирован влево, наоборот, предыдущая страница должна быть анимирована вправо data-left.
.page-1 — это страница, отображаемая в данный момент слева, .page-2 — это страница, отображаемая в данный момент справа, а .page-1-back и .page-2-back отображаются в .paeg-1 и . страница после страницы-2. Они перевернуты по горизонтали за спиной, что не должно быть сложно представить, поэтому вам нужно отразить это по горизонтали с помощью transform:scale:
.page-1-back,
.page-2-back {
transform: scale(-1, 1);
}
И z-индекс .page-1 выше, чем .page-1-back сзади:
.page-1,
.page-2 {
z-index: 1;
}
После такой верстки получается следующий макет:
Затем переверните страницу справа.
2. Анимация перевернутой книги
Это делается для анимации rotateY файла .paper, что очень просто, как показано в следующем коде:
@keyframes flip-to-left {
from {
transform: rotateY(0);
}
to {
transform: rotateY(-180deg);
}
}
.paper[data-right] {
transform-origin: left center;
animation: flip-to-left 2s ease-in-out;
}
Вам нужно установить центр трансформации в центр слева, эффект будет следующим:
Мы обнаружили несколько проблем. Первая проблема заключается в том, что бумага сзади, которая была перевернута, не отображается. Поскольку бумага, которая не отображалась, скрыта в начале, необходимо отобразить бумагу сзади:
.paper {
display: none;
position: absolute;
/* 默认放在右边 */
right: 0;
}
.paper[data-left],
.paper[data-right] {
display: block;
z-index: 1;
}
.paper[data-left] {
right: auto;
left: 0;
}
/* 把相邻的paper显示出来 */
.paper[data-right] + .paper {
display: block;
}
Таким образом, после переворачивания можно отобразить бумагу позади, как показано на следующем рисунке:
Второй вопрос: почему .page-2-back не отображается, но все еще отображается .page-2.Предполагается, что z-индекс .page-2 относительно высок, перекрывая .page-2-back , поэтому, даже если общее свойство поворота изменяется, оно все равно сохраняется.
Таким образом, первый метод может поменять местами высокое и низкое отношение z-индекса при перелистывании наполовину, так что страница-2-назад выше, чем страница-2, но этот метод нелегко контролировать, потому что изменение анимации не является линейным. Да, даже линейный метод негибок и подвержен мерцанию.
Второй метод заключается в настройке отношения translateZ между ними, чтобы значение translateZ страницы-2 было на 1 пиксель выше, чем значение page-2-back, вместо прямой установки отношения z-index. Для того, чтобы translateZ вступил в силу, они должны быть установлены в контейнереПреобразование в стиле сохранения-3d, как показано в следующем коде:
.paper {
transform-style: preserve-3d;
}
.page-1,
.page-2 {
transform: translateZ(1px);
}
Это может сделать дочерний элемент из плоского пространства (flat) в 3-х мерное пространство, translateZ может играть роль, эффект следующий:
Таким образом выходит основной эффект, но я всегда чувствую, что что-то не так, то есть флип немного плоский, и нет эффекта глубины резкости. Говоря о глубине резкости, на ум приходит еще одно свойство CSS.transform-perspective, мы могли бы также добавить к нему перспективу, чтобы увидеть, как это работает:
@keyframes flip-to-left {
from {
transform: perspective(1000px) rotateY(0);
}
to {
transform: perspective(1000px) rotateY(-180deg);
}
}
Результаты, как показано ниже:
Так он выглядит солиднее. Перспективу можно понимать как положение камеры. Чем больше значение, тем дальше камера будет отодвинута. Различные значения сравниваются следующим образом:
Таким образом, анимация поворота книги в основном завершена, то же самое верно и для поворота слева направо. Следующий вопрос — как сформировать анимацию непрерывного перелистывания книги.
3. Постоянно переворачивайте книгу
Вы можете добавить к анимации свойство forwards, чтобы сохранить анимацию в том состоянии, в котором она закончилась в конце:
.paper[data-right] {
transform-origin: left center;
animation: flip-to-left 2s ease-in-out forwards;
}
После остановки связи вышеперечисленных классов нужно обновить заново, например, после перелистывания исходный .page-2-back станет .page-1.
Более научный подход заключается в использованииelement.animateСделайте анимацию, потому что у нее есть обратный вызов, чтобы сообщить нам, что анимация закончилась.Поскольку совместимость этого API не очень хорошая, либо найдите полифилл, либо используйте вышеуказанный метод CSS, а затем используйте setTimeout.Библиотеки для полифилловОтносительно большой, здесь мы по-прежнему используем setTimeout для имитации конца анимации Риск использования setTimeout может быть неточным.
Логика кода относительно проста, то есть нужно найти соответствующий узел dom и установить соответствующий класс или атрибут, но код сложнее, как показано ниже:
let currentPage = 1;
let $ = document.querySelector.bind(document);
$('#next-page').addEventListener('click', goToNextPage);
const flipAnimateTime = 1000;
function goToNextPage () {
// 触发CSS动画
$('.paper[data-right]').setAttribute('data-begin-animate', true);
setTimeout(() => {
// data-right变成data-left
let $rightPaper = $('.paper[data-right]'),
$leftPaper = $('.paper[data-left]');
$rightPaper.removeAttribute('data-right');
$rightPaper.setAttribute('data-left', true);
// data-left没有了
$leftPaper.removeAttribute('data-left');
$leftPaper.querySelector('.page-1').classList.remove('page-1');
$leftPaper.querySelector('.page-1-back').classList.remove('page-1-back');
// 重新设置类的关系
$leftPaper = $rightPaper;
$rightPaper = $leftPaper.nextElementSibling;
$leftPaper.querySelector('.page').classList.add('page-1-back');
$leftPaper.querySelector('.page + .page').classList.add('page-1');
// 如果还有下一页
if ($rightPaper) {
$rightPaper.setAttribute('data-right', true);
$rightPaper.querySelector('.page').classList.add('page-2');
$rightPaper.querySelector('.page + .page').classList.add('page-2-back');
}
currentPage++;
}, flipAnimateTime);
}
Результаты, как показано ниже:
То же самое верно и для страницы налево.
Вот вопрос, а что если пользователь очень быстро кликает на следующую страницу? Если он щелкнет быстро, предыдущее перелистывание страниц не закончилось, что приведет к тому, что код в setTimeout не будет выполнен, и вся модель будет испорчена. Есть два решения.Первое — отключить работу следующей страницы во время процесса перелистывания, но это выглядит не очень дружелюбно.Второе — относиться к процессу перелистывания как к задаче.После следующей страницы щелкнул Или на предыдущей странице, просто вставьте задачу, и каждая задача выполняется синхронно в последовательности.Если длина массива задач больше 1, то сократите время анимации и заставьте ее вращаться быстрее. Подобную обработку я уже делал в "Реализовать эффект переключения карусели внутреннего компонента" обсуждалось и не будет здесь повторяться.
4. Проблема адаптации
Вы можете беспокоиться об изменении структуры dom после окончания анимации, что приведет кСвойство CSS будет мигать при изменении, например, исходная страница-2-назад переворачивается горизонтально, но после того, как она установлена в JS, она становится не горизонтальной.Хотя эффект отображения тот же, будет ли он мигать? Пока результат вычисления макета браузера до и после изменения точно такой же, он не будет мигать, как в приведенном выше примере, но как только смещение будет отличаться на 1 пиксель, мигание будет.
В практическом примере вам может понадобиться тень в 1 пиксель в середине книжного шва, поэтому ширина левой и правой страниц будет не ровно 50%, а минус 1 пиксель, поэтому, если ваше преобразование-происхождение по-прежнему находится слева от центра, то переверните его.Он переместится на 1px вправо.Когда анимация закончится и сбросит состояние, будет исправлено смещение в 1px.В это время будет небольшая вспышка. И когда вы измените значение transform-origin на -1px center, это заставит его сместиться на 1px влево после переворачивания. Так что лучше не отделять тень посередине, вы можете изменить ее, чтобы использовать до/после рисования на каждой странице, и на каждую страницу все равно должно приходиться 50%, так что проблем нет.
Еще один вопрос, который следует учитывать, заключается в том, что использованиеtransform: scale + translateZ может вызвать размытие, прямой пример можно увидеть в этомcodepen, потому что использование translateZ или will-change: transform запускает рендеринг графического процессора, вызывая размытие. Этот процесс может заключаться в том, что браузер делает снимок экрана текущего слоя и отправляет его графическому процессору для расчета. Когда графический процессор увеличивает статическое изображение , он будет размыт. И когда мы удаляем атрибуты, которые имеют эффекты продвижения, такие как translateZ, процесс масштабирования будет размыт, но конечное состояние будет четким. Как показано ниже:
В приведенном выше примере мы использовали transform: scale(-1, 1) для отражения по горизонтали, а затем использовали translateZ(1px) для установления взаимосвязи между верхним и нижним слоями. Теоретически мы используем масштаб, но мы не увеличиваем и не уменьшаем масштаб, и он не должен быть размытым, но Chrome на Windows может четко видеть размытие (Chrome на Mac не будет размытым), удалите translateZ, и он не будет быть размытым. Итак, решение, о котором я подумал, состоит в том, чтобы не использовать translateZ (использовать z-индекс) для слоя в начале, добавить translateZ (и удалить z-индекс) только при запуске анимации и удалить translateZ после окончания анимации.
5. Станьте плагином
Когда вышеуказанные проблемы будут решены, его можно будет превратить в плагин, а пользователю останется только его импортировать, а затем инициализировать, и это будет сделано, не беспокоясь о том, как эти классы меняются.
Более того, поскольку в бумажном контейнере есть две страницы, которые связаны с лицевой и оборотной стороной, как только страница внезапно вставляется в середину, отношение лицевой и оборотной стороны страницы изменится, поэтому эта структура не очень гибкая, лучше всего чтобы генерировать динамически, то есть использовать людей с подключаемыми модулями, просто расположите все страницы рядом, а затем реорганизуйте структуру DOM в подключаемом модуле и поместите две страницы спереди и сзади в одну бумагу.
Затем обсудите реализацию эффекта второй книги.
6. Реализация эффекта перелистывания книги ломаными линиями
Для этого есть готовый плагинturn.js, он очень прост в использовании, мы кратко обсудим его внутреннюю реализацию.
На первый взгляд кажется, что эта штука имеет эффект изогнутой поверхности:
Но на самом деле нет.Этот эффект кривой является визуальным эффектом тени и градиента, который он добавляет.Когда мы убираем градиент фонового изображения, мы можем увидеть его для сравнения:
Градиентного камуфляжа нет, и он сразу выравнивается. Это становится моделью оригами - учитывая лист бумаги и точку сгиба, рассчитайте угол поворота и смещение сложенного. Его исходный код вычисляется внутри функции fold:
В нем есть различные вычисления косинуса и синуса и угловые суждения.Конкретная реализация все еще относительно сложна.Без дополнительных исследований код можно увидеть.turn.js.
Другой вопрос, как достигается эффект отображения треугольника? Это еще один div поверх него:
7. Резюме
В этой статье обсуждается реализация двух эффектов перелистывания книг с акцентом на относительно простой способ перелистывания страниц в целом.Этот метод в основном предназначен для анимации вращения и в то же время открытой перспективы, чтобы создать эффект глубины резкости. и используйте save-3d в сочетании с translateZ для создания отношения между верхним и нижним уровнями, этот метод может иметь проблемы с мерцанием и размытием.Чтобы избежать мерцания при перелистывании, ключевым моментом является обеспечение того, чтобы каждая страница занимала 50% ширина.Проблема размытия возникает из-за использования масштабирования и улучшения графического процессора.Вызвано, поэтому его можно гарантировать только без записи 3D-атрибутов.
Вторая модель эффекта относительно сложна, и кратко анализируются ее принцип и метод реализации. В основном это вычисление угла и смещения оригами, а затем покрытие верхнего слоя div, чтобы скрыть часть, которая не открыта. Затем добавьте тени и градиенты, чтобы создать изогнутый эффект. Эффект перелистывания книги довольно забавный.
"Как производители первого эшелона разрабатывали мини-программы WeChat》