当前位置: 首页 > news >正文

学习threejs,使用kokomi、gsap实现图片环效果

👨‍⚕️ 主页: gis分享者
👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • 1.1 ☘️kokomi.js
      • 1.1.1 ☘️核心功能
      • 1.1.2 ☘️技术实现与依赖
      • 1.1.3 ☘️文档与社区支持
    • 1.2 ☘️gsap.js
      • 1.2.1 ☘️核心功能
      • 1.2.2 ☘️技术实现与依赖
      • 1.2.3 ☘️文档与社区支持
  • 二、🍀图片环效果
    • 1. ☘️实现思路
    • 2. ☘️代码样例


一、🍀前言

本文详细介绍如何基于threejs在三维场景中使用kokomi、gsap实现图片环效果,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️kokomi.js

kokomi.js 是一个基于 Three.js 的轻量级 3D 开发辅助库,旨在简化 Web 端 3D 场景的搭建流程,提升开发效率。其设计灵感源于《原神》角色珊瑚宫心海“统筹全局”的能力特点,通过封装常用功能降低 Three.js 的学习曲线,使开发者能更专注于创意实现。
代码示例:

import * as kokomi from "kokomi.js";
class Sketch extends kokomi.Base {create() {new kokomi.OrbitControls(this); // 添加轨道控制器const box = new kokomi.Box(this); // 创建立方体box.addExisting();this.update((time) => {box.spin(time); // 立方体旋转动画});}
}
const createSketch = () => {const sketch = new Sketch();sketch.create();return sketch;
};
createSketch();

1.1.1 ☘️核心功能

快速场景构建
继承 kokomi.Base 类即可快速启动 3D 场景,无需编写繁琐的初始化代码。
组件化架构
通过 kokomi.Component 封装 3D 元素,保持组件独立状态与动画管理,便于大型项目代码组织。
资源管理
内置 AssetManager 工具,支持 GLTF 模型、纹理、立方体贴图、字体等资源的预加载与配置。

const assetManager = new kokomi.AssetManager(this);
assetManager.load({gltfModel: { url: "model.gltf" },texture: { url: "texture.jpg" }
});

交互集成
集成 three.interactive 库,轻松实现鼠标与触控交互(如拖拽、点击)。
扩展性
提供丰富预置组件(如 OrbitControls、Box),并支持自定义组件开发,满足多样化 3D 场景需求。

1.1.2 ☘️技术实现与依赖

基于 TypeScript 开发,兼容现代 JavaScript 生态,需配合 Three.js 使用。
安装方式

npm install kokomi.js three

开发环境
需 Node.js 环境,支持通过 Git 克隆仓库获取源码,并使用 npm 脚本运行示例或启动开发服务器。

1.1.3 ☘️文档与社区支持

kokomi 官方文档

1.2 ☘️gsap.js

GSAP(GreenSock Animation Platform)是一个高性能、跨平台的 JavaScript 动画库,旨在简化复杂动画效果的实现。其核心优势在于提供精确的动画控制、丰富的缓动效果和广泛的浏览器兼容性,适用于网页设计、游戏开发、移动应用开发等多个领域。
代码示例:

gsap.to("#navbar", { height: "100px", duration: 0.5, ease: "power1.inOut" });

1.2.1 ☘️核心功能

动画控制
补间动画:通过 gsap.to()、gsap.from() 和 gsap.fromTo() 方法,可以轻松实现元素的位移、旋转、缩放、透明度变化等动画效果。
时间轴管理:使用 gsap.timeline() 可以创建复杂的动画序列,控制多个动画的顺序和并行执行。
响应式动画:支持根据视口大小或其他条件动态调整动画参数。
缓动效果
GSAP 内置了丰富的缓动函数库,涵盖线性、弹性、弹跳、正弦等多种类型,如 “power1.in”、“elastic.out” 等。通过设置 ease 属性,可以为动画添加自然和富有表现力的效果。
插件系统
ScrollTrigger:允许用最少的代码创建滚动动画,例如当元素滚动到视口特定位置时触发动画。
MorphSVG:支持 SVG 路径的变形动画。
Draggable:实现拖拽功能,增强交互性。
跨平台兼容性
支持所有主流浏览器,包括移动设备,确保动画在不同环境下的一致性表现。

1.2.2 ☘️技术实现与依赖

安装方式 : Object

// 通过 npm 安装
npm install gsap
// 通过 CDN 引入
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>

模块化设计 : Object
GSAP 3.x 版本将 TweenMax 和 TweenLite 的功能整合到 gsap 对象中,支持按需引入插件,减少体积。

1.2.3 ☘️文档与社区支持

gasp 官网

二、🍀图片环效果

1. ☘️实现思路

使用kokomi、gsap以及自定义着色器实现图片环效果

2. ☘️代码样例

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><link href="https://cdn.jsdelivr.net/gh/alphardex/aqua.css/dist/aqua.min.css" rel="stylesheet"><title>图片环</title><style>body {margin: 0;overflow: hidden;background: black;font-family: "Inter", sans-serif;}#sketch {width: 100vw;height: 100vh;background: black;}.loading span {animation: blur 1.5s calc(var(--i) / 5 * 1s) alternate infinite;}@keyframes blur {to {filter: blur(5px);}}</style>
</head>
<body>
<!-- On-screen Mobile Controls -->
<div id="sketch"></div>
<div class="fixed z-5 top-0 left-0 loader-screen w-screen h-screen transition-all duration-300 bg-white"><div class="absolute hv-center"><div class="loading text-3xl tracking-widest whitespace-no-wrap"><span style="--i: 0">L</span><span style="--i: 1">O</span><span style="--i: 2">A</span><span style="--i: 3">D</span><span style="--i: 4">I</span><span style="--i: 5">N</span><span style="--i: 6">G</span></div></div>
</div>
<div class="hero-dom opacity-0"><div class="absolute hv-center"><div class="flex flex-col items-center space-y-2 whitespace-no-wrap"><div class="text-8xl text-white">RING</div><div class="text-xl" style="color: #8a8a8a;">Just drag and scroll~</div></div></div>
</div>
<!-- Import map for Three.js ES Modules -->
<script type="module">import * as kokomi from "https://esm.sh/kokomi.js";import * as THREE from "https://esm.sh/three";import gsap from "https://esm.sh/gsap";const fragmentShader = /* glsl */ `uniform float iTime;uniform vec2 iResolution;uniform vec2 iMouse;uniform sampler2D tDiffuse;varying vec2 vUv;uniform vec3 uBgColor;uniform float uRGBShiftIntensity;uniform float uGrainIntensity;uniform float uVignetteIntensity;uniform float uTransitionProgress;highp float random(vec2 co){highp float a=12.9898;highp float b=78.233;highp float c=43758.5453;highp float dt=dot(co.xy,vec2(a,b));highp float sn=mod(dt,3.14);return fract(sin(sn)*c);}vec3 grain(vec2 uv,vec3 col,float amount){float noise=random(uv+iTime);col+=(noise-.5)*amount;return col;}vec4 RGBShift(sampler2D tex,vec2 uv,float amount){vec2 rUv=uv;vec2 gUv=uv;vec2 bUv=uv;float noise=random(uv+iTime)*.5+.5;vec2 offset=amount*vec2(cos(noise),sin(noise));rUv+=offset;gUv+=offset*.5;bUv+=offset*.25;vec4 rTex=texture(tex,rUv);vec4 gTex=texture(tex,gUv);vec4 bTex=texture(tex,bUv);vec4 col=vec4(rTex.r,gTex.g,bTex.b,gTex.a);return col;}vec3 vignette(vec2 uv,vec3 col,vec3 vigColor,float amount){vec2 p=uv;p-=.5;float d=length(p);float mask=smoothstep(.5,.3,d);mask=pow(mask,.6);float mixFactor=(1.-mask)*amount;col=mix(col,vigColor,mixFactor);return col;}float sdCircle(vec2 p,float r){return length(p)-r;}vec3 transition(vec2 uv,vec3 col,float progress){float ratio=iResolution.x/iResolution.y;// circlevec2 p=uv;p-=.5;p.x*=ratio;float d=sdCircle(p,progress*sqrt(2.2));float c=smoothstep(-.2,0.,d);col=mix(vec3(1.),col,1.-c);return col;}void main(){vec2 uv=vUv;vec4 tex=RGBShift(tDiffuse,uv,uRGBShiftIntensity);vec3 col=tex.xyz;col=grain(uv,col,uGrainIntensity);col=vignette(uv,col,uBgColor,uVignetteIntensity);col=transition(uv,col,uTransitionProgress);gl_FragColor=vec4(col,1.);}`;class Sketch extends kokomi.Base {create() {const config = {bgColor: "#0c0c0c"};const params = {transitionProgress: 0,enterProgress: 0,rotateSpeed: 15};this.renderer.setClearColor(new THREE.Color(config.bgColor), 1);this.camera.position.set(0, 0, 16);// new kokomi.OrbitControls(this);const sumFormula = (n) => {return (n * (n + 1)) / 2;};const isOdd = (n) => {return n % 2 === 1;};const circleCount = 3;const circleImgCountUnit = 12;const circleImgTotalCount = circleImgCountUnit * sumFormula(circleCount);const resourceList = [...Array(circleImgTotalCount).keys()].map((_, i) => ({name: `tex${i + 1}`,type: "texture",// path: `https://picsum.photos/id/${i + 1}/320/400`path: `./images/photos/${i + 1}.jpg`}));const am = new kokomi.AssetManager(this, resourceList);am.on("ready", () => {document.querySelector(".loader-screen")?.classList.add("hollow");const material = new THREE.MeshBasicMaterial();const r = 6.4;const scale = 0.8;const rings = [];const lines = [];for (let i = 0; i < circleCount; i++) {const c1 = sumFormula(i) * circleImgCountUnit;const c2 = sumFormula(i + 1) * circleImgCountUnit;const textures = Object.values(am.items).slice(c1, c2);const ring = new THREE.Group();this.scene.add(ring);rings.push(ring);const meshes = textures.map((tex, j) => {const line = new THREE.Group();ring.add(line);lines.push(line);const imgScale = 0.005 * scale * (i * 0.36 + 1);const width = tex.image.width * imgScale;const height = tex.image.height * imgScale;const geometry = new THREE.PlaneGeometry(width, height);const mat = material.clone();mat.map = tex;mat.needsUpdate = true;const mesh = new THREE.Mesh(geometry, mat);const r2 = r * (i + 1);const ratio = j / (c2 - c1);const angle = ratio * Math.PI * 2;mesh.position.x = r2;mesh.rotation.z = -Math.PI / 2;line.rotation.z = angle;line.add(mesh);return mesh;});}const ce = new kokomi.CustomEffect(this, {fragmentShader,uniforms: {uBgColor: {value: new THREE.Color(config.bgColor)},uRGBShiftIntensity: {value: 0.0025},uGrainIntensity: {value: 0.025},uVignetteIntensity: {value: 0.8},uTransitionProgress: {value: 0}}});ce.addExisting();this.ce = ce;const wheelScroller = new kokomi.WheelScroller();wheelScroller.listenForScroll();const dragDetecter = new kokomi.DragDetecter(this);dragDetecter.detectDrag();dragDetecter.on("drag", (delta) => {wheelScroller.scroll.target -= (delta.x || delta.y) * 2;});this.update(() => {wheelScroller.syncScroll();rings.forEach((ring, i) => {ring.rotation.z +=0.0025 *(isOdd(i) ? -1 : 1) *(1 + wheelScroller.scroll.delta) *params.rotateSpeed;});lines.forEach((line) => {line.position.z =-THREE.MathUtils.lerp(0,100,THREE.MathUtils.mapLinear(wheelScroller.scroll.delta,0,1000,0,1)) + THREE.MathUtils.lerp(10, 0, params.enterProgress);});this.ce.customPass.material.uniforms.uTransitionProgress.value =params.transitionProgress;});const anime = () => {const t1 = gsap.timeline();t1.to(params, {transitionProgress: 1,duration: 1,ease: "power1.inOut"}).fromTo(params,{enterProgress: 0,rotateSpeed: 10},{enterProgress: 1,rotateSpeed: 1,duration: 1.5,ease: "power1.inOut"},"-=1").to(".hero-dom",{opacity: 1},"-=1");};anime();});}}const sketch = new Sketch("#sketch");sketch.create();
</script>
</body>
</html

效果如下:
在这里插入图片描述
参考源码

相关文章:

  • 有一个私人做慈善的网站企业网站营销实现方式
  • jsp做网站下载图片深圳优化seo排名
  • 公司做的网站如何开启伪静态猪肉价格最新消息
  • 网站建设的利益网络营销推广专家
  • 除了WordPress等长春seo推广
  • 网站建设的类型或分类百度识图在线识别
  • 独家战略!谷子科技“芯”技术联姻浙江卫视
  • 跟着Carl学算法--哈希表
  • Kafka如何保证消息可靠?
  • 构建你的 AI 模块宇宙:Spring AI MCP Server 深度定制指南
  • 哈希表理论与算法总结
  • TCP/UDP协议深度解析(一):UDP特性与TCP确认应答以及重传机制
  • Leaking GAN
  • Netty内存池核心PoolArena源码解析
  • 搭建智能问答系统,有哪些解决方案,比如使用Dify,LangChain4j+RAG等
  • 《C++初阶之类和对象》【初始化列表 + 自定义类型转换 + static成员】
  • Python光学玻璃库opticalglass
  • IP证书在网络安全中的作用
  • Windows驱动开发最新教程笔记2025(一)名词解释
  • Label Studio安装和使用
  • ABP VNext + BFF(Backend for Frontend)模式:Angular/React 专用聚合层
  • 总结设置缓存的时机
  • 七天学会SpringCloud分布式微服务——01
  • 基于C#实现(WinForm)P2P聊天小程序
  • 操作系统---内存管理之虚拟内存
  • React性能优化:父组件如何导致子组件重新渲染及避免策略