实现了电子书阅读器的翻页模式:滚动翻页、滑动翻页、覆盖翻页和仿真翻页。其中仿真翻页使用
StPageFlip库
实现,平滑翻页使用column
属性+Transform 平移
完成,覆盖翻页使用绝对定位层叠
+Transform 平移
完成。
一般电子书阅读器都有 4 种翻页模式:
- 滚动翻页:通过滚动整个页面来展现内容,一般会有“懒加载”机制,即当页面滚动到接近底部时,自动加载更多内容
- 滑动翻页:类似于滚动翻页,通常模拟了“滑动”动作,用户通过左右滑动屏幕来切换章节或页面内容。
- 覆盖翻页:通过模拟页面翻转的方式,产生页面在物理空间中被覆盖的效果,通常模仿书本翻页的真实感。
- 仿真翻页:模拟了纸质书籍翻页的物理效果,页面在用户的控制下产生折叠、弯曲、翻转的效果,仿佛书页被翻过的真实感觉。
仿真翻页
仿真翻页使用StPageFlip库
实现,如下所示:
import { EBOOK_TEXT_LIST } from "@/_mock/book";
import { Radio } from "antd";
import { useCallback, useRef, useState } from "react";
import HTMLFlipBook from "react-pageflip";
import { styled } from "styled-components";
interface iProps {
width?: number;
height?: number;
}
const options = [
{ label: "仿真", value: "turn" },
{ label: "滚动", value: "scroll" },
{ label: "平滑", value: "smooth" },
{ label: "覆盖", value: "cover" },
];
export default function PageFlip({ width = 298, height = 420 }: iProps) {
const [mode, setMode] = useState(options[0].value);
const [currentPage, setCurrentPage] = useState(0);
const totalPages = EBOOK_TEXT_LIST.length;
// 处理仿真翻页事件
const handleFlip = useCallback((e) => {
setCurrentPage(e.data);
console.log(`翻页至第 ${e.data + 1} 页`);
}, []);
return (
<Container>
{/* 模式切换按钮 */}
<Radio.Group
block
options={options}
optionType="button"
buttonStyle="solid"
value={mode}
onChange={(e) => setMode(e.target.value)}
/>
<div className="mt-2 mb-2">
第 {currentPage + 1} 页 / 共 {totalPages} 页
</div>
{/* 仿真翻页模式 */}
{mode === "turn" && (
<HTMLFlipBook
width={width}
height={height}
showCover={false}
size="fixed"
startPage={currentPage}
className="flipBook"
onFlip={handleFlip}
maxShadowOpacity={0.5} // 优化阴影效果
mobileScrollSupport={false} // 启用移动端触摸支持
>
{EBOOK_TEXT_LIST.map((o: any, idx) => (
<div className="demoPage" key={o.id}>
<p>第{idx + 1}页</p>
<p>{o.text}</p>
</div>
))}
</HTMLFlipBook>
)}
</Container>
);
}
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
.demoPage {
border: 1px solid #ccc;
background-color: rgb(224, 236, 224);
padding: 24px;
box-sizing: border-box;
touch-action: none; // 防止移动端滚动冲突
}
`;
没找到控制单页显示的配置,默认根据宽度自动判断的,所以先通过宽度来控制单页显示
。。。后续再研究下文档,看能不能配置吧
<Card title="翻页模式切换(StPageFlip)" style={{ width: "400px" }}>
<PageFlipMode />
</Card>
渲染效果,如图 ebook-3.png 所示:
滚动翻页
滚动翻页没做特殊处理,直接渲染完一章的内容了,自己可以补充懒加载逻辑,滚动翻页加载完毕。
这种方式是最简单的了,但是对用户体验不太友好。
/* 滚动翻页模式 */
mode === "scroll" && (
<div className="scrollBook" style={{ width, height }}>
{EBOOK_TEXT_LIST.map((o: any, idx) => (
<div className="demoPage" key={o.id}>
<p>第{idx + 1}页</p>
<p>{o.text}</p>
</div>
))}
</div>
);
const Container = styled.div`
.scrollBook {
position: relative;
overflow-x: hidden;
overflow-y: auto;
.demoPage {
width: 100%;
height: 100%;
}
}
`;
渲染效果,如图 ebook-5.png 所示:
平滑翻页(column)
在上下滑动模式下通过 column
属性直接适配成左右翻页,实现自动分页效果,不需要复杂计算
column多栏布局
可以将内容分隔成多列进行展示,类似于报纸的栏目排版,用来模拟分页效果。
具体可以查看官网:MDN Web Docs 之 columns
- column-width: 每栏的宽度
- column-count: 分栏个数
- column-gap: 每栏之间的间隔
通过column-width
分列,控制容器的 translateX
属性,将内容渲染到视口中,同时增加动画属性可以在切换分页的时候设置动画效果。
但是存在性能问题:
- 一次性需要将所有内容全部排列并渲染,内容过多时会影响页面性能
- 翻页只能支持滑动效果,不能支持“覆盖翻页”、“仿真翻页”效果,需要另外处理
渲染效果,如图所示:
覆盖翻页
覆盖翻页:通常指的是新页面覆盖旧页面,可能带有 3D 旋转或者淡入淡出的效果。需要改变布局方式,从横向排列改为层叠定位,并通过 z-index 和动画来控制页面的显示。
- 平滑翻页模式
- 核心原理:CSS Transform 平移 + Transition 动画
- DOM 结构:横向排列的 flex 布局
- 层级管理:线性顺序排列
- 覆盖翻页模式
- 核心原理:CSS 3D 旋转 + Perspective 空间透视
- DOM 结构:绝对定位层叠的 div 结构
- 层级管理:z-index 倒序堆叠
如下所示,基本形式和平滑翻页
一致,只展示关键代码了:
const Container = styled.div`
&.coverBook {
position: relative;
overflow: hidden;
perspective: 1000px;
.tools {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 20px;
z-index: 999;
}
}
`;
const Page = styled.div`
position: absolute;
top: 0;
left: 0;
background: rgb(224, 236, 224);
transition: all 0.8s cubic-bezier(0.23, 1, 0.32, 1);
backface-visibility: hidden;
padding: 24px;
p {
text-indent: 2em;
}
`;
- 动态位置控制:通过 transform 属性控制页面水平位置
- 当前页:translateX(0)
- 已翻过的页:translateX(-100%)
- 未翻的页:translateX(100%)
- 透明度控制:
- 当前页 opacity: 1
- 其他页 opacity: 0
- 层级控制:
- 当前页使用最高 z-index(totalPages + 1)
- 其他页保持原有层级顺序
渲染效果,如图 ebook-8.png 所示:
其他系列文章: