这个页面实现了一个基于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事件,动态调整相机和渲染器的尺寸,确保在窗口大小改变时保持正确的渲染效果。
---各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!