电子书阅读器之翻页模式

151 阅读4分钟

实现了电子书阅读器的翻页模式:滚动翻页、滑动翻页、覆盖翻页和仿真翻页。其中仿真翻页使用StPageFlip库实现,平滑翻页使用column属性+Transform 平移完成,覆盖翻页使用绝对定位层叠+Transform 平移完成。

一般电子书阅读器都有 4 种翻页模式:

  1. 滚动翻页:通过滚动整个页面来展现内容,一般会有“懒加载”机制,即当页面滚动到接近底部时,自动加载更多内容
  2. 滑动翻页:类似于滚动翻页,通常模拟了“滑动”动作,用户通过左右滑动屏幕来切换章节或页面内容。
  3. 覆盖翻页:通过模拟页面翻转的方式,产生页面在物理空间中被覆盖的效果,通常模仿书本翻页的真实感。
  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 所示:

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 所示:

ebook-5.png

平滑翻页(column)

在上下滑动模式下通过 column 属性直接适配成左右翻页,实现自动分页效果,不需要复杂计算

column多栏布局可以将内容分隔成多列进行展示,类似于报纸的栏目排版,用来模拟分页效果。

具体可以查看官网:MDN Web Docs 之 columns

  • column-width: 每栏的宽度
  • column-count: 分栏个数
  • column-gap: 每栏之间的间隔

通过column-width分列,控制容器的 translateX 属性,将内容渲染到视口中,同时增加动画属性可以在切换分页的时候设置动画效果。

但是存在性能问题:

  1. 一次性需要将所有内容全部排列并渲染,内容过多时会影响页面性能
  2. 翻页只能支持滑动效果,不能支持“覆盖翻页”、“仿真翻页”效果,需要另外处理

ebook3.png

ebook4.png

截屏2025-05-27 16.26.11.png

渲染效果,如图所示:

ebook-6.png

ebook-7.png

覆盖翻页

覆盖翻页:通常指的是新页面覆盖旧页面,可能带有 3D 旋转或者淡入淡出的效果。需要改变布局方式,从横向排列改为层叠定位,并通过 z-index 和动画来控制页面的显示。

  1. 平滑翻页模式
    • 核心原理:CSS Transform 平移 + Transition 动画
    • DOM 结构:横向排列的 flex 布局
    • 层级管理:线性顺序排列
  2. 覆盖翻页模式
    • 核心原理:CSS 3D 旋转 + Perspective 空间透视
    • DOM 结构:绝对定位层叠的 div 结构
    • 层级管理:z-index 倒序堆叠

如下所示,基本形式和平滑翻页一致,只展示关键代码了:

截屏2025-05-27 16.19.11.png

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;
  }
`;
  1. 动态位置控制:通过 transform 属性控制页面水平位置
    • 当前页:translateX(0)
    • 已翻过的页:translateX(-100%)
    • 未翻的页:translateX(100%)
  2. 透明度控制:
    • 当前页 opacity: 1
    • 其他页 opacity: 0
  3. 层级控制:
    • 当前页使用最高 z-index(totalPages + 1)
    • 其他页保持原有层级顺序

渲染效果,如图 ebook-8.png 所示:

ebook-8.png

其他系列文章: