Как добиться эффекта 3D флип-книги

внешний интерфейс JavaScript CSS
Как добиться эффекта 3D флип-книги

В этой статье в основном обсуждается реализация следующих двух анимаций флипбука:

Во-первых, это эффект полного перелистывания страницы:

Эффект этого полного переворота страницы в основном заключается в выполнении анимации 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