普通视图

发现新文章,点击刷新页面。
昨天以前首页

性能提升60%:前端性能优化终极指南

作者 石小石Orz
2025年8月15日 09:50

hi,我是石小石~


性能优化一直是前端绕不开的话题。页面加载慢、交互卡顿,不仅影响用户体验,还可能直接流失用户。本文将从加载、运行、构建、网络四个环节,系统梳理前端能想到的各种性能优化手段,帮助我们尽可能的提升前端页面性能。

加载性能优化:更快呈现首屏

加载阶段的目标是尽快把可见内容展示给用户,减少白屏和首屏等待时间。

资源压缩与代码混淆

资源压缩的核心目标就是——让浏览器传输和解析的文件尽可能小,这样加载速度自然就快了。

  • 代码压缩:通过移除 HTML、CSS、JS 中的空格、注释,并缩短变量名来减小文件体积。打包阶段可借助 ViteWebpack 等构建工具内置或插件化的压缩方案如 Terser自动完成。
  • 图片优化:优先使用 WebPAVIF 等高压缩比格式,并通过 imagemintinypng 等工具进一步压缩体积;对于大量小图标,可使用 CSS Sprites 合并成一张精灵图,减少 HTTP 请求数量。

代码分割(Code Splitting)

代码分割就是把项目代码按功能或页面拆成多个小文件,用户访问时只加载当前需要的部分,如路由懒加载:

React

import React, { Suspense } from 'react';
const Chart = React.lazy(() => import('./Chart'));

<Suspense fallback={<div>Loading...</div>}>
  <Chart />
</Suspense>

Vue

const routes = [
  { path: '/', component: () => import('@/views/Home.vue') },
  { path: '/about', component: () => import('@/views/About.vue') }
];

Tree Shaking摇树优化

Tree Shaking 是一种在打包阶段自动删除未使用代码的优化技术,能让最终文件更小、加载更快。它依赖 ES Module (import/export) 的静态结构来分析哪些代码实际被用到,没用到的就会被“摇掉”。

Vite(基于 Rollup)和大多数现代构建工具里,Tree Shaking 在生产构建时是默认开启的,只需要:

  • 使用 ES Module 语法,而不是 require
  • 避免全局副作用代码,或在 package.json 中声明:
{ "sideEffects": false }

CDN 加速

CDN 加速就是把网站的静态资源(JS、CSS、图片、字体等)分发到全球多个节点,让用户就近从最近的服务器获取资源,从而减少网络延迟、提高加载速度。

项目中,可以将静态资源(JS、CSS、图片、字体)部署到 阿里云或腾讯云等CDN,让用户从最近的节点获取资源。

减少渲染阻塞

渲染阻塞是指浏览器在解析 HTML 时,遇到某些资源(如 CSS、同步 JS)会暂停页面渲染,直到这些资源加载并解析完成,这会直接延迟首屏显示时间。减少渲染阻塞的核心,就是让关键内容先呈现,非关键资源延后或异步加载。

  • CSS 优化:将首屏必需的 CSS 抽取为关键 CSS直接内联到 HTML,其余样式文件通过 media 属性或延迟加载方式引入。
<link rel="stylesheet" href="style.css" media="print" onload="this.media='all'">
  • JS 优化:对非首屏必须执行的 JS 使用 deferasync,避免阻塞 HTML 解析。
<script src="app.js" defer></script>
<script src="analytics.js" async></script>
  • 字体加载优化:使用 font-display: swap,让文字在字体加载前先用系统字体渲染,避免长时间空白。
@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  font-display: swap;
}

预加载与预渲染

预加载与预渲染的目标是提前把可能需要的资源或页面准备好,让用户在点击或访问时几乎无等待。

  • preload:提前加载关键资源(如字体、CSS、首屏图片),确保它们在渲染时已经准备就绪。
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
  • prefetch:利用浏览器空闲时间加载未来可能使用的资源(如下一页的 JS 文件),等真正用到时直接从缓存读取。
<link rel="prefetch" href="/next-page.js">

SSR 与 SSG

这两种方式都是在用户请求到达前,就把页面 HTML 准备好,从而减少白屏时间。

  • SSR(Server-Side Rendering) :由服务端实时生成 HTML 并返回给浏览器,用户首屏几乎立刻可见,后续由前端接管交互。适合需要动态数据的场景,比如电商、资讯类网站。react中我们可以借助next.js实现这一需求。
  • SSG(Static Site Generation) :在构建阶段一次性生成所有静态 HTML 文件,用户访问时直接从服务器或 CDN 获取,速度极快且可离线缓存。适合内容更新不频繁的站点,比如博客、文档站。React 中可以使用 Astro 或 Next.js SSG 模式,Vue 生态中则有 VitePress 和 VuePress 等优秀工具。

Gzip/Brotli 压缩

在服务器端启用 Gzip 或 Brotli 压缩,可以显著减小传输文件的体积,尤其是 JS、CSS、HTML 等文本类资源,通常能减少 60%~80% 的网络传输量。开启Gzip压缩,只需要在vite或webapck中开启配置,并在nginx中配置即可。

依赖共享

在多页面应用(MPA)或微前端场景中,把公共依赖(如 React、Vue、Lodash 等)提取出来,通过 浏览器缓存 或 CDN 共享加载,可以避免重复下载同一依赖,减少首屏加载体积。

  • Webpack 中可通过 SplitChunksPlugin 配置 vendor 包。
  • Vite 中可利用 optimizeDepsmanualChunks 手动拆分依赖。

运行阶段优化

运行阶段的目标是让页面在交互过程中保持流畅不卡顿,通过优化渲染策略和代码,可以有效减少性能浪费。

避免不必要的重绘与回流

  • 回流(Reflow) :当元素的大小、位置、布局发生变化时,浏览器需要重新计算布局,并重新渲染页面。
  • 重绘(Repaint) :当元素外观(如颜色、背景)改变但布局没变时,只需要重新渲染外观

回流是性能杀手,它会引发页面重新计算布局,尤其是在复杂 DOM 结构下,代价非常高。重绘成本低一些,但频繁发生也会卡顿。

虚拟滚动/列表

当你要渲染一个 1 万行的长表格,如果一次性全渲染,浏览器直接卡到怀疑人生。虚拟列表的思路其实很简单只渲染当前可见区域的内容,滚动时替换 DOM 节点,保证 DOM 数量稳定。Vue和React也有很多开源库可以使用。

防抖与节流

防抖节流 都是用来优化高频事件触发的技术,但原理和应用场景不同:

  • 防抖(Debounce)
    在事件频繁触发时,只在最后一次触发后 等待一段时间 才执行回调。如果在等待时间内事件又被触发,就重新计时。

适用场景:搜索框输入、窗口大小调整(resize)、表单实时验证等。

  • 节流(Throttle)
    在事件频繁触发时,保证 固定时间间隔 内只执行一次回调,即使事件被多次触发也不会更快执行。

适用场景:滚动(scroll)、拖拽(drag)、鼠标移动(mousemove)等。

图片懒加载

在网页加载时,只加载首屏或当前可见区域内的图片,其他图片等用户滚动到可见区域时再加载。这种方式称为图片懒加载,它有以下优点:

  • 减少首屏加载时间,提升页面打开速度
  • 降低首屏网络请求数量,节省带宽
  • 减轻服务器瞬时压力

在HTML 中,原生懒加载写法如下:

<img src="image.jpg" loading="lazy" alt="example" />

当然,社区也有对应的开源库,如React的react-lazyload,vue的vue-lazyload

Web Worker 分担计算压力

JavaScript 是单线程运行的,如果在主线程执行复杂计算(如文件解析、加密、压缩),会阻塞 UI 渲染。
Web Worker 允许我们在浏览器中开启一个独立的线程来执行 JavaScript 代码,把耗时、计算量大的任务放在这个线程中执行。

它的常见用途包括:

  • 大量数据计算(加密、解密、数据分析)
  • 图片、视频的压缩与处理
  • 大文件解析(CSV、JSON)
  • 实时数据流处理

注意,它依旧不是传统意义上的多线程。

使用案例:

worker.js

self.onmessage = e => {
  let sum = 0;
  for (let i = 0; i < e.data; i++) sum += i;
  self.postMessage(sum);
};

📄 App.jsx

import React from 'react';

export default function App() {
  const runWorker = () => {
    const worker = new Worker(new URL('./worker.js', import.meta.url));
    worker.postMessage(1e8); // 计算 1 亿次
    worker.onmessage = e => {
      alert(`结果: ${e.data}`);
      worker.terminate();
    };
  };

  return <button onClick={runWorker}>开始计算</button>;
}

内存泄漏监控与优化

内存泄漏会让网页在长时间运行后越来越卡,最终崩溃。常见原因有:

  • 未清理的定时器 / 事件监听
  • 被引用的 DOM 节点未释放
  • 闭包中保留了不必要的变量

优化手段:

  • 使用 Chrome Performance 工具分析内存快照
  • 组件卸载时(React useEffect 返回清理函数 / Vue beforeUnmount)释放资源

构建优化

压缩与混淆

在 React/Vue 等前端项目里,压缩与混淆基本都是构建工具自动完成的,你几乎不需要手动去配置。

第三方库优化

分包策略

分包策略是指将打包后的代码分成多个 bundle,避免一次性加载所有资源,提高首屏速度。常见策略如按路由分包按组件分包按依赖分包。它能延迟非必要资源加载,提升首屏加载速度。

被分包的依赖,如第三方库,打包后hash值不变,重新部署会使用缓存文件,也能提升首屏加载速度。

在Vite中,使用它也很简单:

// Vite Rollup 分包配置
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          react: ['react', 'react-dom'], // 单独打包 react
          lodash: ['lodash'] // lodash 单独打包
        }
      }
    }
  }
}

懒加载第三方资源

这种方式类似路由懒加载,延迟非关键资源的下载,按需加载第三方库或模块,避免在初始加载时引入全部依赖,减轻首页负担。

// React 动态导入
const Chart = React.lazy(() => import('./Chart'));

export default function Page() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <Chart />
    </React.Suspense>
  );
}

依赖排除

构建时将某些依赖排除,不打包进 bundle,而是从 CDN 加载。它可以有效减少 bundle 体积,利用缓存和边缘节点加速首页访问。

但并非依赖排除的越多越好,js请求也需要网络。

如:

// Vite 配置
export default {
  build: {
    rollupOptions: {
      external: ['vue'], // 排除 vue
    }
  }
}

网络优化

TCP 预连接

提前与目标服务器建立 TCP + TLS 连接,减少请求延迟。

<link rel="preconnect" href="https://cdn.example.com">

DNS 预解析

提前解析域名,减少 DNS 查询延迟。

<link rel="dns-prefetch" href="//cdn.example.com">

请求合并

对多个重复请求进行合并处理,前端可以通过防抖或判断接口状态实现。

<script setup>
import { ref } from 'vue';
import axios from 'axios';

const timeoutId = ref(null);

function debounce(fn, delay) {
  return function(...args) {
    if (timeoutId.value) clearTimeout(timeoutId.value);
    timeoutId.value = setTimeout(() => {
      fn(...args);
    }, delay);
  };
}

function fetchData() {
  axios.get('http://api/gcshi)  // 使用示例API
    .then(response => {
      console.log(response.data);
    })
}

const debouncedFetchData = debounce(fetchData, 300);
</script>

如何查看网页性能

浏览器内置工具

网络面板

查看网络请求,查看所有请求的耗时、大小、缓存命中情况,找出大文件、重复请求、慢响应资源。

性能面板

  • FCP(First Contentful Paint,首次内容绘制时间)
  • LCP(Largest Contentful Paint,最大内容绘制时间)
  • CLS(Cumulative Layout Shift,累计布局偏移)
  • TTI(Time to Interactive,可交互时间)

Lighthouse面板

自动化检测性能、可访问性、SEO 等综合评分,并给出优化建议。

第三方平台工具

WebPageTest

  • URL:www.webpagetest.org/
  • 模拟不同地区、网络条件下的页面加载,查看瀑布图、渲染时间线。

GTmetrix

  • URL:gtmetrix.com/
  • 类似 Lighthouse,但报告更细,可看首屏截图、视频回放,方便对比优化前后效果。

性能监控与上报

  • Sentry:可采集性能、JS 错误、慢接口
  • 阿里云 ARMS / 字节火山监控:支持前端 + 后端链路追踪
  • 自建埋点系统:结合 Performance API,将指标上报到日志系统

总结

本文梳理了一些比较常见的前端可行性能优化方案,有遗漏的地方,欢迎大家补充。

React生态蓝图梳理:前端、全栈与跨平台全景指南

作者 石小石Orz
2025年8月12日 23:37

Hi,我是石小石!


很久没和大家聊 React 了,今天心血来潮,想带大家一起梳理一下 2025 最新的 React 生态蓝图,帮你在做技术选型和架构设计时少走弯路。
发展到今天,React 早已不只是一个前端 UI 库,而是形成了一个覆盖 前端开发、全栈架构、跨平台应用、性能优化、AI 驱动开发 的完整生态圈。
接下来,我会从 核心语言 → 脚手架与框架 → 状态管理 → UI 组件 → 性能优化 → 移动端 → 测试 → AI 开发辅助 → 全栈与部署 这条完整链路,梳理 2025 年最新的 React 技术栈,并为每个环节提供 官方链接、技术特点、适用场景、优缺点分析,让你能一篇掌握全貌。

React 核心与基础演进

React 19(2024 年 12 月发布)带来了不少实用更新。新增了 Actions API,让异步状态更新更简单,同时支持阻塞和非阻塞渲染,提升了渲染控制能力。静态站点生成和 React Server Components 也更成熟了。

Hooks 方面,除了 useDeferredValueuseTransition 继续优化渲染优先级,新增的 useFormStatususeOptimisticuseActionState等 Hooks 让交互体验更顺畅。

渲染机制依然基于成熟的 Fiber 架构,支持可中断和增量更新,结合实验性的 Concurrent Mode,响应速度和用户体验有明显提升。React 的核心理念没变,声明式 UI、虚拟 DOM、组件化和单向数据流,依然是写出高质量代码的基础。

附官网文档:react.nodejs.cn/learn

脚手架、构建与运行时

  • Vite:启动快到飞起,热更新(HMR)响应迅速,非常适合轻量级 SPA 和快速迭代开发。
  • Bun:新晋的 JavaScript 运行时,集打包、SSR 和依赖管理于一身,能给 React 项目带来更快的构建和运行体验。
  • Next.js 15:完美支持 React 19,内置 React Server Components、Server Functions,还有基于 Rust 的 Turbopack,轻松搞定混合渲染(SSR、SSG、ISR)和 AI 优化代码,打造全栈和边缘部署应用效率满满。
  • Remix(现 React Router v7) :2024 年 11 月正式更名,结合前端路由和全栈特性,支持 SSR,是 Next.js 的有力竞争者。
  • Astro:主打静态优先,JS 体积极小,只在必须交互的组件上做 hydration,非常适合内容驱动或性能敏感的项目。

状态管理与数据获取

  • TanStack Query(React Query) :自动缓存数据、后台刷新,让服务端状态管理更智能,性能和体验双提升,数据请求处理更简单。
  • Zustand / Jotai / Recoil / MobX / XState:一系列轻量级状态管理方案,适合局部或全局状态,各有侧重,写法简洁直观,且易于扩展。
  • Redux Toolkit:Redux 的现代升级版,集成状态管理和数据获取,代码结构清晰,是复杂项目的稳定选择。
  • React Server Components / Functions:把数据处理和渲染逻辑放到服务器端,减轻客户端负担,性能和用户体验都能明显提升。

选取指南:

场景 推荐库
需要稳定且可预测的大型复杂应用 Redux + Redux Toolkit
快速开发,追求轻量和简洁 Zustand 发音: /ˈzuːstænd/
组件状态依赖复杂,异步多 Recoil / Jotai 发音: /ˈriːkɔɪl/ /ˈdʒoʊtaɪ/
需要响应式、自动追踪依赖 MobX 发音: /mɒb ɛks/
状态流程明确且复杂,流程化管理 XState
混合局部与全局状态管理 Zustand + Jotai 组合使用

路由与全栈结构

  • React Router v7:支持 SSR 和全栈开发,适合轻量级多页面应用,灵活度高。
  • Next.js 自带路由系统:基于文件系统的路由,简单直观,非常适合企业级项目和结构复杂的页面。
  • TanStack Router:新晋的 TypeScript 优选路由库,设计现代,未来计划与 React Server Components 深度集成,值得持续关注。

UI 组件与样式体系

  • Tailwind CSS + shadcn/ui:原子化 CSS 带来极高定制自由度,配合无样式且注重可访问性的组件库,开发效率和体验都非常棒。
  • Material UI / Chakra UI / DaisyUI / Ant Design:主流组件生态,提供丰富现成的样式和交互组件,适合快速迭代和企业级项目。
  • CSS Modules / Styled Components / Emotion:模块化样式方案中,CSS Modules 通过限定 CSS 作用域避免全局冲突,简单易用,适合渐进式迁移;Styled Components 支持在 JS 中编写动态样式,方便主题切换和复用,但有一定运行时开销;Emotion 则更轻量灵活,支持静态提取和 CSS-in-JS,兼顾性能和开发体验。

未完待续

写不动了,留个坑,明天继续整理

视差悬停特效:鼠标跟随的沉浸式交互体验

作者 石小石Orz
2025年8月11日 13:46

大家好,我是石小石,小册《油猴脚本实战指南》作者。


视差悬停效果

最近在 Trae 官网发现了一个有趣的交互效果:当鼠标悬停在图标上时,图标会根据鼠标的移动产生跟随位移,看起来很炫酷。

于是我用AI问了一下,才知道这种效果叫“视差悬停效果”,挺有意思。

视差悬停效果”(Parallax Hover Effect)是一种常见的前端视觉交互效果,模拟景深原理,让页面元素在鼠标移动时产生错位移动,营造出立体或空间感,增强用户交互体验。

用Trae实现一个类似效果

先让Trae帮忙实现一个类似效果:

生成的代码如下:

<template>
  <!-- 外层容器,负责监听鼠标移动和离开事件 -->
  <div class="logo-container" @mousemove="handleMouseMove" @mouseleave="resetEffect">
    <!-- 中心 Logo 容器,包含所有球体 -->
    <div class="logo" ref="logo">
      <!-- 循环生成球体 -->
      <div
        v-for="(sphere, index) in spheresConfig"
        :key="index"
        class="sphere"
        ref="spheres"
        :style="{
          width: sphere.size + 'px',       // 球体直径
          height: sphere.size + 'px',      // 球体直径
          top: sphere.top,                 // 定位:上边距
          left: sphere.left,               // 定位:左边距
          right: sphere.right,             // 定位:右边距
          bottom: sphere.bottom,           // 定位:下边距
          backgroundColor: sphere.color    // 球体颜色
        }"
      ></div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 中心 Logo 元素引用
const logo = ref(null)
// 所有球体元素的引用
const spheres = ref([])

// 球体配置数组
const spheresConfig = [
  { size: 60, top: '40px', left: '40px', color: '#3498db' },
  { size: 60, top: '40px', right: '40px', color: '#e74c3c' },
  { size: 60, bottom: '40px', left: '40px', color: '#2ecc71' },
  { size: 60, bottom: '40px', right: '40px', color: '#f39c12' },
  { size: 70, top: '120px', left: '120px', color: '#9b59b6' },
  { size: 50, top: '70px', left: '170px', color: '#1abc9c' },
  { size: 45, top: '180px', left: '70px', color: '#d35400' },
  { size: 40, top: '150px', right: '60px', color: '#27ae60' },
  { size: 35, top: '210px', right: '120px', color: '#8e44ad' },
  { size: 25, top: '30px', left: '150px', color: '#c0392b' }
]

// 鼠标移动事件
const handleMouseMove = (e) => {
  const container = e.currentTarget
  const rect = container.getBoundingClientRect()

  // 计算鼠标在容器中的相对位置(-1 ~ 1)
  const x = ((e.clientX - rect.left) / rect.width - 0.5) * 2
  const y = ((e.clientY - rect.top) / rect.height - 0.5) * 2

  // 中心 Logo 随鼠标旋转
  logo.value.style.transform = `rotateX(${-y * 20}deg) rotateY(${x * 20}deg)`

  // 每个球体根据鼠标位置做视差位移
  spheres.value.forEach((sphere, index) => {
    const sphereX = x * 35 * Math.cos(index * 0.8) // X 方向位移
    const sphereY = y * 35 * Math.sin(index * 0.8) // Y 方向位移
    const sphereZ = Math.abs(x * y) * 70 + index * 8 // Z 方向位移(层次感)

    sphere.style.transform = `translate3d(${sphereX}px, ${sphereY}px, ${sphereZ}px)`
  })
}

// 鼠标移出时恢复初始位置
const resetEffect = () => {
  logo.value.style.transform = 'rotateX(0) rotateY(0)'
  spheres.value.forEach(sphere => {
    sphere.style.transform = 'translate3d(0, 0, 0)'
  })
}
</script>

<style scoped>
/* 外层容器,定义大小和透视深度 */
.logo-container {
    position: relative;
    width: 300px;
    height: 300px;
    margin: 100px auto;
    perspective: 1200px;
}

/* 中心 Logo 容器 */
.logo {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform 0.1s ease-out;
}

/* 球体公共样式 */
.sphere {
    position: absolute;
    border-radius: 50%;
    transition: transform 0.3s ease-out;
    box-shadow: inset -8px -8px 16px rgb(0 0 0 / 30%), 
        8px 16px rgb(255 255 255 / 30%);
}
</style>

效果还是很不错的:

这个demo的原理其实很简单,主要分为三步:

  1. 获取鼠标相对位置
  2. 模拟相机视角变化
  3. 实现分层错位运动

获取鼠标相对位置

const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
const y = ((e.clientY - rect.top) / rect.height - 0.5) * 2;

getBoundingClientRect() 是浏览器 DOM API 中非常常用的方法,用于获取元素相对于视口(viewport)的尺寸和位置。

  • xy 都是范围 -11 的值,中心是 0,代表鼠标在容器里的相对位置。
  • 这个范围让后续计算方便做正负方向运动。

模拟相机视角变化

logo.value.style.transform = `rotateX(${-y * 20}deg) rotateY(${x * 20}deg)`;

这一步的主要目的是让整体结构跟着鼠标动,模拟相机视角变化。最简单的就是通过CSS的transform实现3D位置变化。

这里的 20 是旋转最大角度,让效果不会太夸张。

分层错位运动

要想让球体有空间感,就要实现球体视差位移,同样的可以使用transform属性。

spheres.value.forEach((sphere, index) => {
  const sphereX = x * 35 * Math.cos(index * 0.8);
  const sphereY = y * 35 * Math.sin(index * 0.8);
  const sphereZ = Math.abs(x * y) * 70 + index * 8;
  
  sphere.style.transform = `translate3d(${sphereX}px, ${sphereY}px, ${sphereZ}px)`;
});

“视差”的核心是让每个球体产生不同速度和运动方向:

  • 每个球体的 X 轴偏移sphereX = 鼠标横向偏移 * 35 * 一个和球序号有关的余弦函数。
    → 余弦函数让每个球的偏移方向和幅度有差异,避免动作统一死板。
  • Y 轴偏移sphereY 类似,用正弦函数产生错落。
  • Z 轴偏移sphereZ 通过鼠标位置和球的索引控制,让不同球体在“深度”上产生不同位移,制造层次感。
  • translate3d 是 CSS 3D 变换,直接告诉浏览器把球体往三个方向移动。

当然,在鼠标离开时,我们需要所有元素慢慢回到原点,避免停留在中间状态。

logo.value.style.transform = 'rotateX(0) rotateY(0)';
spheres.value.forEach(sphere => {
  sphere.style.transform = 'translate3d(0, 0, 0)';
});

开源库

市面上有一些专门做视差或3D交互的库,可以帮我们快速实现效果。

Parallax.js

  • 经典轻量级视差库,支持基于鼠标移动的多层元素视差效果。
  • 用法简单,适合基础悬停视差。
  • GitHub 地址

Vanilla-tilt.js

  • 类似 Tilt.js,功能更全面,支持触摸事件和更多配置。
  • 纯原生 JS,无依赖,性能好。
  • GitHub 地址

react-parallax-tilt (React 生态)

  • React 版本的Tilt效果库,适合 React 项目。
  • 支持自定义视差和旋转,易于集成。
  • GitHub 地址
❌
❌