HTML&CSS:哇塞!Three.js 打造的 3D 交互平面,鼠标滑动纹理如梦幻般变形!

这个页面实现了一个基于Three.js的3D交互效果,用户可以通过鼠标移动在平面上产生动态的纹理变形和阴影效果。


大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能解决问题,私信回复源码两字,我会发送完整的压缩包给你。

演示效果

HTML&CSS

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>公众号关注:前端Hardy</title>
    <script src="https://cdn.jsdelivr.net/npm/three/build/three.min.js"></script>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>

    <head>
        <title>Interaction</title>
    </head>
    <script>
        // 图标地址太长~后台联系我私发
        let texture = ''
        let shadow_texture = ''

        var scene = new THREE.Scene();

        let aspect = window.innerWidth / window.innerHeight;
        let camera_distance = 8;
        const camera = new THREE.OrthographicCamera(
            -camera_distance * aspect,
            camera_distance * aspect,
            camera_distance,
            -camera_distance,
            0.01,
            1000
        );

        camera.position.set(-0, -10, 5);
        camera.lookAt(0, 0, 0);

        var renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        scene.background = new THREE.Color(0xffffff);

        const geometry_sphere = new THREE.SphereGeometry(0.25, 32, 16);
        const material_sphere = new THREE.MeshBasicMaterial({
            color: 0xff0000,
            transparent: true,
            opacity: 0,
            depthWrite: false
        });
        const sphere = new THREE.Mesh(geometry_sphere, material_sphere);
        scene.add(sphere);

        var geometry_hit = new THREE.PlaneGeometry(500, 500, 10, 10);
        const material_hit = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            transparent: true,
            opacity: 0,
            depthWrite: false
        });
        var hit = new THREE.Mesh(geometry_hit, material_hit);
        hit.name = "hit";
        scene.add(hit);

        var geometry = new THREE.PlaneGeometry(15, 15, 100, 100);
        let shader_material = new THREE.ShaderMaterial({
            uniforms: {
                uTexture: { type: "t", value: new THREE.TextureLoader().load(texture) },
                uDisplacement: { value: new THREE.Vector3(0, 0, 0) }
            },

            vertexShader: `
  varying vec2 vUv;
  uniform vec3 uDisplacement;
  
float easeInOutCubic(float x) {
  return x < 0.5 ? 4. * x * x * x : 1. - pow(-2. * x + 2., 3.) / 2.;
}

float map(float value, float min1, float max1, float min2, float max2) {
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}  

  void main() {
   vUv = uv;
   vec3 new_position = position; 
 
    vec4 localPosition = vec4( position, 1.);
    vec4 worldPosition = modelMatrix * localPosition;

    float dist = (length(uDisplacement - worldPosition.rgb));
    float min_distance = 3.;

    if (dist < min_distance){
      float distance_mapped = map(dist, 0., min_distance, 1., 0.);
      float val = easeInOutCubic(distance_mapped) * 1.; 
      new_position.z +=  val;
    }
    
   
   gl_Position = projectionMatrix * modelViewMatrix * vec4(new_position,1.0);
  }
`,
            fragmentShader: ` 
    varying vec2 vUv;
    uniform sampler2D uTexture;
    void main()
    {
       vec4 color =  texture2D(uTexture, vUv); 
       gl_FragColor = vec4(color) ;    
    }`,
            transparent: true,
            depthWrite: false,
            side: THREE.DoubleSide
        });

        var plane = new THREE.Mesh(geometry, shader_material);
        plane.rotation.z = Math.PI / 4;
        scene.add(plane);

        let shader_material_shadow = new THREE.ShaderMaterial({
            uniforms: {
                uTexture: {
                    type: "t",
                    value: new THREE.TextureLoader().load(shadow_texture)
                },
                uDisplacement: { value: new THREE.Vector3(0, 0, 0) }
            },

            vertexShader: `
  varying vec2 vUv;
  varying float dist;
  uniform vec3 uDisplacement;

  void main() {
   vUv = uv;
   
   vec4 localPosition = vec4( position, 1.);
   vec4 worldPosition = modelMatrix * localPosition;
   dist = (length(uDisplacement - worldPosition.rgb));
   
   gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
  }
`,
            fragmentShader: ` 
    varying vec2 vUv;
    varying float dist;
    uniform sampler2D uTexture;
    
    float map(float value, float min1, float max1, float min2, float max2) {
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}  

    void main()
    {
       vec4 color =  texture2D(uTexture, vUv); 
       float min_distance = 3.;

       if (dist < min_distance){
        float alpha = map(dist, min_distance, 0., color.a , 0.);
        color.a  = alpha;
        }
       
       gl_FragColor = vec4(color) ;    
    }`,
            transparent: true,
            depthWrite: false,
            side: THREE.DoubleSide
        });

        var plane_shadow = new THREE.Mesh(geometry, shader_material_shadow);
        plane_shadow.rotation.z = Math.PI / 4;
        scene.add(plane_shadow);

        window.addEventListener("resize", onWindowResize);

        const raycaster = new THREE.Raycaster();
        const pointer = new THREE.Vector2();
        window.addEventListener("pointermove", onPointerMove);

        function onPointerMove(event) {
            pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
            pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;

            raycaster.setFromCamera(pointer, camera);
            const intersects = raycaster.intersectObject(hit);

            if (intersects.length > 0) {
                sphere.position.set(
                    intersects[0].point.x,
                    intersects[0].point.y,
                    intersects[0].point.z
                );

                shader_material.uniforms.uDisplacement.value = sphere.position;
                shader_material_shadow.uniforms.uDisplacement.value = sphere.position;
            }
        }

        function render() {
            requestAnimationFrame(render);
            renderer.render(scene, camera);
        }

        render();

        function onWindowResize() {
            aspect = window.innerWidth / window.innerHeight;

            camera.left = -camera_distance * aspect;
            camera.right = camera_distance * aspect;
            camera.top = camera_distance;
            camera.bottom = -camera_distance;

            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }
    </script>
</body>

</html>

HTML 结构

  • body:定义了页面的主体内容。
  • script:包含JavaScript代码,用于实现3D场景的渲染和交互功能。

CSS 样式

  • html, body:设置页面的外边距和内边距为0。禁止页面内容溢出,确保全屏显示。

JavaScript功能说明

Three.js初始化

  • 创建了一个3D场景(THREE.Scene)。
  • 定义了一个正交相机(THREE.OrthographicCamera),用于2D渲染。
  • 创建了一个WebGL渲染器(THREE.WebGLRenderer),并将其附加到页面的body元素中。
  • 设置场景背景颜色为白色(0xffffff)。

3D对象创建

  • 创建了一个球体(THREE.SphereGeometry),用于表示鼠标悬停的焦点。
  • 创建了一个平面(THREE.PlaneGeometry),用于渲染主要的纹理效果。
  • 创建了一个阴影平面(THREE.PlaneGeometry),用于渲染阴影效果。

着色器材质

  • 使用THREE.ShaderMaterial定义了两个着色器材质:
  • 主要平面的材质,通过顶点着色器和片段着色器实现纹理的动态变形效果。
  • 阴影平面的材质,通过顶点着色器和片段着色器实现阴影的动态效果。

交互功能

  • 使用THREE.Raycaster和pointermove事件监听器,实现鼠标悬停时的交互效果。
  • 当鼠标移动时,计算鼠标与平面的交点,并更新球体的位置和着色器的位移参数。

渲染循环

  • 使用requestAnimationFrame实现持续的渲染循环,确保场景的动态效果能够实时更新。

窗口调整

  • 监听resize事件,动态调整相机和渲染器的尺寸,确保在窗口大小改变时保持正确的渲染效果。

---各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

相关推荐
烛阴9 分钟前
从零到RESTful API:Express路由设计速成手册
javascript·后端·express
ElasticPDF-新国产PDF编辑器25 分钟前
Vue PDF Annotation plugin library online API examples
javascript·vue.js·pdf
鱼樱前端27 分钟前
Vite 工程化深度解析与最佳实践
前端·javascript
鱼樱前端34 分钟前
Webpack 在前端工程化中的核心应用解析-构建老大
前端·javascript
Moment34 分钟前
多人协同编辑算法 —— CRDT 算法 🐂🐂🐂
前端·javascript·面试
小付同学呀40 分钟前
前端快速入门学习4——CSS盒子模型、浮动、定位
前端·css·学习
ElasticPDF-新国产PDF编辑器2 小时前
Vue 项目使用 pdf.js 及 Elasticpdf 教程
javascript·vue.js·pdf
勘察加熊人3 小时前
vue记忆卡牌游戏
javascript·vue.js·游戏
yanyu-yaya4 小时前
第三章 react redux的学习之redux和react-redux,@reduxjs/toolkit依赖结合使用
javascript·学习·react.js
江城开朗的豌豆4 小时前
使用Plotly.js创建炫酷红外轮廓热力图 - Vue组件封装实战
前端·javascript·vue.js