npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@happyqu/flipbook

v0.1.3

Published

A responsive WebGL 3D flipbook engine for Vue 3 and TypeScript.

Readme

WebGL FlipBook Engine

简体中文 | English

基于 TypeScript、Three.js、WebGL Shader 和 Vue 3 的 H5 3D 翻书引擎。页面使用真实 Mesh 变形完成卷曲、翻面和阴影效果,并针对桌面端与移动端提供不同的阅读交互。

功能

  • WebGL Page Curl:纸页弯曲、翻面、动态光影和书脊折痕
  • 封面模式:初始显示单页封面,翻开后进入双页书本
  • 双页排版:普通跨页、首页封面和末页单页自动居中
  • 跟手翻页:拖动距离实时控制纸页弯曲进度,松手后完成或回弹
  • 移动端单页聚焦:保留双页展开结构,画面聚焦当前阅读页
  • 快速短划:双页内部平滑切换焦点,到页边缘后连续翻到下一跨页
  • 双指缩放:默认支持 1x 到 3x
  • 自由拖动:放大后可向上下左右任意移动,双指缩放可无缝衔接单指拖动
  • 导航居中:书本被移动后点击上一页或下一页,会先平滑回到当前页中心再翻页
  • 响应式布局:适配常见手机尺寸、横屏和动态视口
  • 纹理管理:邻近页面预加载,离开缓存范围的 GPU 纹理自动释放
  • 多种操作:触控、鼠标、点击左右区域、方向键、PageUp 和 PageDown

页面资源

引擎接收按阅读顺序排列的图片 URL,不在浏览器运行时解析 PDF。推荐在服务端或构建阶段将 PDF 转换为 WebP:

page001.webp
page002.webp
page003.webp
...

建议移动端页面宽度约为 1200px;需要高清展示时可使用 1500–2000px。所有页面最好保持相同宽高比。

const pages = [
  '/magazine/page001.webp',
  '/magazine/page002.webp',
  '/magazine/page003.webp',
]

当 cover 为 true 时,数组第一张图片作为封面。示例阅读顺序为:

封面 → [第 1 页 | 第 2 页] → [第 3 页 | 第 4 页] → ...

本地开发

环境要求:Node.js 18+。

npm install
npm run dev

常用命令:

npm run typecheck  # TypeScript 类型检查
npm run test       # 运行单元测试
npm run build      # 构建演示站点
npm run build:lib  # 构建组件库

Vue 3 使用

容器必须具有明确的宽度和高度,引擎会自动填满容器。

npm install @happyqu/flipbook

ES Module / Vite

<script setup lang="ts">
import { ref } from 'vue'
import { VueFlipBook, type PageChangeDetail } from '@happyqu/flipbook'
import '@happyqu/flipbook/style.css'

type ReaderRef = InstanceType<typeof VueFlipBook>

const reader = ref<ReaderRef>()
const zoom = ref(1)

const pages = [
  '/magazine/page001.webp',
  '/magazine/page002.webp',
  '/magazine/page003.webp',
  '/magazine/page004.webp',
]

const handleChange = (detail: PageChangeDetail) => {
  console.log('当前页:', detail.currentPage)
}
</script>

<template>
  <div class="reader-container">
    <VueFlipBook
      ref="reader"
      :pages="pages"
      :cover="true"
      :shadow="true"
      :duration="720"
      :min-zoom="1"
      :max-zoom="3"
      @page-change="handleChange"
      @zoom-change="zoom = $event.zoom"
      @ready="console.log('FlipBook ready')"
    />
  </div>

  <div class="controls">
    <button @click="reader?.previous()">上一页</button>
    <button @click="reader?.animateZoom(zoom - 0.25)">缩小</button>
    <button @click="reader?.animateZoom(zoom + 0.25)">放大</button>
    <button @click="reader?.next()">下一页</button>
  </div>
</template>

<style scoped>
.reader-container {
  width: 100%;
  height: min(78dvh, 820px);
  min-height: 320px;
}
</style>

组件会从第一张成功加载的图片自动识别页面比例,并根据容器空间自动选择单页或跨页、计算相机距离。使用者只需要用 CSS 设置外层容器尺寸。高级场景可通过像素值覆盖内部安全留白:

.reader-container {
  --flipbook-inset: 16px;
}

CommonJS

const { FlipBook, VueFlipBook } = require('@happyqu/flipbook')
require('@happyqu/flipbook/style.css')

Vue 子路径

import { VueFlipBook } from '@happyqu/flipbook/vue'
import '@happyqu/flipbook/style.css'

浏览器 CDN / UMD

UMD 版本依赖全局的 Vue 和 THREE,适合不使用打包工具的页面:

<link
  rel="stylesheet"
  href="https://unpkg.com/@happyqu/flipbook/dist/flipbook.css"
/>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<script type="module">
  import * as THREE from 'https://unpkg.com/[email protected]/build/three.module.min.js'
  window.THREE = THREE
  await import('https://unpkg.com/@happyqu/flipbook/dist/flipbook.umd.js')

  const { FlipBook, VueFlipBook } = window.FlipBook
</script>

Props

| 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | pages | string[] | 必填 | 按阅读顺序排列的页面图片 URL | | view | 'auto' \| 'single' \| 'spread' | 'auto' | 自动选择、强制单页聚焦或强制完整跨页 | | pageRatio | number | 首张有效图片的宽高比 | 页面宽度除以高度;仅在需要覆盖自动识别时传入 | | shadow | boolean | true | 是否启用翻页阴影 | | duration | number | 760 | 自动翻页动画时长,单位 ms | | cover | boolean | true | 是否将第一张图片作为单页封面 | | minZoom | number | 1 | 最小缩放比例 | | maxZoom | number | 3 | 最大缩放比例 | | preloadRadius | number | 2 | 当前页前后预加载的页面范围 | | pixelRatio | number | 设备像素比(最大 2) | 自定义 WebGL 渲染像素比 | | ariaLabel | string | 3D flipbook reader | 阅读器画布的无障碍名称 | | pageTurnSound | boolean \| string | true | 启用内置翻页音效;也可传入自定义音频 URL,设为 false 可关闭 | | pageTurnSoundVolume | number | 0.55 | 翻页音量,范围为 0 到 1 |

事件

| 事件 | 参数 | 说明 | | --- | --- | --- | | ready | PageStateDetail | 初始页面加载完成 | | page-change | PageChangeDetail | 当前跨页或移动端聚焦页发生变化 | | flip-start | FlipDetail | 开始实际的纸张翻转 | | flip-progress | FlipProgressDetail | 翻页进度发生变化,progress 范围为 0 到 1 | | flip-end | FlipDetail | 翻页成功且目标跨页加载完成 | | flip-cancel | FlipCancelDetail | 翻页手势撤销或被其他操作中断 | | layout-change | LayoutChangeDetail | 容器尺寸、页面比例或单双页布局发生变化 | | zoom-change | ZoomChangeDetail | 缩放比例发生变化 | | pan-change | PanChangeDetail | 缩放后的平移位置发生变化 | | page-error | PageErrorDetail | 某张页面图片加载失败 | | error | unknown | Vue 组件初始化或更新页面失败 |

原生引擎与 Vue 组件使用相同的事件名和参数。error 仅由 Vue 包装层触发;原生方法的错误通过异常或 Promise rejection 返回。

主要事件参数:

interface PageStateDetail {
  leftPage: number
  rightPage: number
  currentPage: number
  view: 'single' | 'spread'
  pageRatio: number
  totalPages: number
  isFirstPage: boolean
  isLastPage: boolean
  canPrevious: boolean
  canNext: boolean
}

interface PageChangeDetail extends PageStateDetail {
  reason: 'flip' | 'focus' | 'go-to' | 'pages-update' | 'layout'
}

interface FlipDetail {
  direction: 'next' | 'previous'
  fromPage: number
  toPage: number
}

interface FlipProgressDetail extends FlipDetail {
  progress: number
}

interface FlipCancelDetail extends FlipProgressDetail {
  reason: 'gesture' | 'interrupted'
}

成功翻页的事件顺序为 flip-start → flip-progress → page-change → flip-end。取消翻页时最后触发 flip-cancel,不会触发 page-change。

页面索引均为从 0 开始的图片数组索引;业务界面可根据是否包含封面自行转换为展示页码。

暴露方法

| 方法 | 返回值 | 说明 | | --- | --- | --- | | next() | Promise<boolean> | 下一页;成功切换时返回 true | | previous() | Promise<boolean> | 上一页;成功切换时返回 true | | goTo(pageIndex) | Promise<void> | 跳转到指定图片索引 | | setView(view) | void | 设置自动、单页或跨页视图 | | setPageRatio(pageRatio?) | void | 覆盖页面比例;省略参数恢复图片自动识别 | | setZoom(zoom) | void | 立即设置缩放比例,适合实时手势 | | animateZoom(zoom) | Promise<void> | 平滑设置缩放比例,适合按钮操作 | | resetZoom() | void | 恢复到 1x 并清除拖动偏移 | | getEngine() | FlipBook \| undefined | 获取底层引擎实例 |

原生 TypeScript 使用

import {
  FlipBook,
  type FlipCancelDetail,
  type FlipDetail,
  type FlipProgressDetail,
  type PageChangeDetail,
  type PageErrorDetail,
} from '@happyqu/flipbook'

const container = document.querySelector<HTMLElement>('#reader')!

const book = new FlipBook(container, {
  pages,
  view: 'auto',
  cover: true,
  shadow: true,
  duration: 720,
  minZoom: 1,
  maxZoom: 3,
  preloadRadius: 2,
})

await book.ready
await book.next()
await book.animateZoom(1.5)

原生引擎通过容器派发以下 CustomEvent:

container.addEventListener('page-change', (event) => {
  const detail = (event as CustomEvent<PageChangeDetail>).detail
  console.log(detail)
})

container.addEventListener('flip-start', (event) => {
  const { direction, fromPage, toPage } = (
    event as CustomEvent<FlipDetail>
  ).detail
  console.log('开始翻页', direction, fromPage, toPage)
})

container.addEventListener('flip-progress', (event) => {
  const { progress } = (event as CustomEvent<FlipProgressDetail>).detail
  console.log('翻页进度', progress)
})

container.addEventListener('flip-end', (event) => {
  const { toPage } = (event as CustomEvent<FlipDetail>).detail
  console.log('翻页完成', toPage)
})

container.addEventListener('flip-cancel', (event) => {
  const detail = (event as CustomEvent<FlipCancelDetail>).detail
  console.log('翻页取消', detail.reason, detail.progress)
})

container.addEventListener('page-error', (event) => {
  const { pageIndex, url, error } = (
    event as CustomEvent<PageErrorDetail>
  ).detail
  console.error('页面加载失败', pageIndex, url, error)
})

不再使用时应释放 WebGL 与事件资源:

book.dispose()

交互规则

桌面端

  • 点击或拖动页面左右区域进行翻页
  • 使用 ←、→、PageUp、PageDown 导航
  • 放大后可自由拖动书本
  • 放大且移动过位置时,上一页/下一页会先平滑回到当前页中心,再开始翻页

移动端 100%

  • 双页书本保持展开,视口始终聚焦当前阅读页
  • 横向慢拖可在同一跨页的左右页之间移动
  • 到达当前页边缘后书本停止移动,继续拖动会直接带动纸页翻开
  • 快速短划直接导航,并从松手位置连续完成移动或翻页
  • 纵向拖动被锁定,避免阅读画面漂移

移动端放大后

  • 双指捏合实时放大或缩小
  • 抬起一根手指后,另一根手指可以直接继续拖动
  • 上下左右拖动不设置位置边界
  • 点击缩放按钮时建议调用 animateZoom(),双指手势应调用 setZoom()

性能说明

  • 使用分段页面 Mesh 和 Vertex Shader 计算卷曲,而非 CSS 旋转
  • 使用 requestAnimationFrame 驱动动画
  • 仅按需加载当前跨页附近的图片,并自动释放远离当前页的 GPU 纹理
  • 只保留当前页附近的纹理,超出缓存范围后调用 texture.dispose()
  • pixelRatio 可用于限制高 DPI 设备的渲染开销

浏览器要求

需要支持 WebGL、ES Modules、Pointer Events 和 ResizeObserver 的现代浏览器。建议在目标微信 WebView、iOS Safari 和 Android Chrome 上使用真实杂志素材进行最终兼容性测试。