这个页面实现了一个带有3D动画效果的网页,用户可以看到一个3D场景中的书本随机放置并旋转。页面使用了Three.js库和GSAP库来实现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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>
<script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script>
<title>公众号关注:前端Hardy</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #000;
font-family: monospace;
color: white;
}
body {
font-family: Bague, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: rgb(48, 68, 67);
background: #628f84;
background: #768ca8;
background: #d7dddd;
background: #e4eeef;
}
body:after {
content: "";
position: fixed;
top: -10rem;
left: -10rem;
width: calc(100vw + 100rem);
height: calc(100vh + 100rem);
background-image: image-set(url("https://i.imgur.com/N0STPcn.png") type("image/png"));
opacity: 0.4;
z-index: 1000;
animation: noise 1s steps(2) infinite;
}
@keyframes noise {
0% {
transform: translate3d(0, 9rem, 0);
}
50% {
transform: translate3d(-9rem, -4rem, 0);
}
100% {
transform: translate3d(-7rem, 0, 0);
}
}
.header {
position: fixed;
top: 0;
width: 100%;
padding: 1rem;
text-align: center;
background: rgba(0, 0, 0, 0.5);
z-index: 10;
}
#canvas-container {
height: 200vh;
width: 100%;
position: relative;
}
canvas {
position: absolute;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
display: block;
z-index: 1;
}
.overlay-text {
position: absolute;
top: 50%;
left: 15%;
transform: translateY(-50%);
text-align: left;
z-index: 2;
font-size: 35px;
font-weight: 400;
mix-blend-mode: difference;
color: grey;
max-width: 50%;
line-height: 1.3;
pointer-events: none;
}
.color-change {
color: inherit;
}
</style>
<body>
<div id="canvas-container"></div>
<div class="overlay-text">
<div>
<span class="color-change">
I write fiction to take readers on a journey that explores the human condition,both good and bad.
</span>
</div>
</div>
<script>
window.addEventListener('load', init, false);
function init() {
createWorld();
createLights();
createPrimitive();
animation();
}
var Theme = {
primary: 0xd7dddd,
secundary: 0x0000FF,
danger: 0xFF0000,
darker: 0x101010
};
var scene, camera, renderer, controls;
var _group = new THREE.Group();
function createWorld() {
const _width = window.innerWidth;
const _height = document.getElementById('canvas-container').clientHeight;
scene = new THREE.Scene();
scene.fog = new THREE.Fog(Theme.primary, 9, 13);
scene.background = null;
camera = new THREE.PerspectiveCamera(35, _width / _height, 1, 1000);
camera.position.set(0, 0, 10);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(_width, _height);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableZoom = false;
controls.update();
document.getElementById('canvas-container').appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
const _width = window.innerWidth;
const _height = window.innerHeight;
renderer.setSize(_width, _height);
camera.aspect = _width / _height;
camera.updateProjectionMatrix();
}
function createLights() {
const hemiLight = new THREE.HemisphereLight(Theme.primary, Theme.darker, 1);
const dirLight = new THREE.DirectionalLight(0xFFFFFF, 1);
dirLight.position.set(10, 20, 20);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 5000;
dirLight.shadow.mapSize.height = 5000;
dirLight.penumbra = 0.8;
scene.add(hemiLight);
scene.add(dirLight);
}
function CreateBook() {
this.mesh = new THREE.Object3D();
const geo_cover = new THREE.BoxGeometry(2.4, 3, 0.05);
const lmo_cover = new THREE.BoxGeometry(0.05, 3, 0.59);
const ppr_cover = new THREE.BoxGeometry(2.3, 2.8, 0.5);
const dartmouthGreens = [0x475b47];
const randomGreen = dartmouthGreens[Math.floor(Math.random() * dartmouthGreens.length)];
const mat = new THREE.MeshPhongMaterial({ color: randomGreen });
const mat_paper = new THREE.MeshPhongMaterial({ color: 0xFFFFFF });
const _cover1 = new THREE.Mesh(geo_cover, mat);
const _cover2 = new THREE.Mesh(geo_cover, mat);
const _lomo = new THREE.Mesh(lmo_cover, mat);
const _paper = new THREE.Mesh(ppr_cover, mat_paper);
[_cover1, _cover2, _lomo, _paper].forEach(mesh => {
mesh.castShadow = true;
mesh.receiveShadow = true;
});
_cover1.position.z = 0.3;
_cover2.position.z = -0.3;
_lomo.position.x = 2.4 / 2;
this.mesh.add(_cover1, _cover2, _lomo, _paper);
}
function isTooClose(newObj, others, minDistance = 1.5) {
const newPos = newObj.position;
for (let existing of others) {
const dx = newPos.x - existing.position.x;
const dy = newPos.y - existing.position.y;
const dz = newPos.z - existing.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < minDistance) return true;
}
return false;
}
function createPrimitive() {
const placedBooks = [];
const a = 2;
for (let i = 0; i < 12; i++) {
const _object = new CreateBook();
const s = 0.1 + Math.random() * 0.4;
_object.mesh.scale.set(s, s, s);
let tries = 0;
do {
_object.mesh.position.x = (Math.random() - 0.5) * a * 2;
_object.mesh.position.y = (Math.random() - 0.5) * a * 2;
_object.mesh.position.z = (Math.random() - 0.5) * a * 2;
tries++;
} while (isTooClose(_object.mesh, placedBooks) && tries < 20);
_object.mesh.rotation.x = Math.random() * 2 * Math.PI;
_object.mesh.rotation.y = Math.random() * 2 * Math.PI;
_object.mesh.rotation.z = Math.random() * 2 * Math.PI;
TweenMax.to(_object.mesh.rotation, 8 + Math.random() * 8, {
x: (Math.random() - 0.5) * 0.5,
y: (Math.random() - 0.5) * 0.5,
z: (Math.random() - 0.5) * 0.5,
yoyo: true,
repeat: -1,
ease: Sine.easeInOut,
delay: 0.05 * i
});
_group.add(_object.mesh);
placedBooks.push(_object.mesh);
}
scene.add(_group);
_group.position.x = 2;
}
function animation() {
_group.rotation.x -= 0.003;
_group.rotation.y -= 0.003;
_group.rotation.z -= 0.003;
controls.update();
requestAnimationFrame(animation);
renderer.render(scene, camera);
}
</script>
</body>
</html>
HTML 结构
- canvas-container:用于放置Three.js渲染的3D内容。
- overlay-text:包含覆盖在3D内容上的文本。
- script:包含页面的JavaScript代码,用于初始化和控制3D场景。
CSS 样式
-
html, body:设置外边距和内边距为0。禁止内容溢出,确保全屏显示。设置背景颜色为黑色。设置字体为monospace,颜色为白色。
-
body:after:添加一个固定背景图,使用image-set设置图片。设置透明度为0.4。使用@keyframes定义动画效果,使背景图在不同位置之间移动。
-
.header:固定在页面顶部,宽度为100%。设置内边距、文本居中、背景颜色和z-index。
-
#canvas-container:设置高度为200vh,宽度为100%。设置为相对定位。
-
canvas:设置为绝对定位,覆盖整个视口。设置为块级元素,确保全屏显示。设置z-index为1。
-
.overlay-text:设置为绝对定位,垂直居中。设置文本对齐方式、字体大小、权重、混合模式和颜色。设置最大宽度和行高。设置pointer-events为none,避免鼠标事件干扰。
JavaScript功能说明
初始化
- init():在页面加载完成后调用,初始化3D场景、灯光、物体和动画。
- createWorld():创建3D场景、相机、渲染器和轨道控制。
- createLights():创建环境光和方向光。
- createPrimitive():创建多个3D物体(书本),并随机放置在场景中。
- animation():定义动画循环,更新场景的旋转和渲染。
3D场景
- 使用Three.js库创建3D场景。
- 使用THREE.PerspectiveCamera创建透视相机。
- 使用THREE.WebGLRenderer渲染场景。
- 使用THREE.OrbitControls控制相机的旋转和缩放。
灯光
- 使用THREE.HemisphereLight创建环境光。
- 使用THREE.DirectionalLight创建方向光,并启用阴影。
物体
- 使用THREE.BoxGeometry创建书本的几何形状。
- 使用THREE.MeshPhongMaterial创建材质。
- 使用TweenMax库为书本添加动画效果。
动画
- 使用requestAnimationFrame实现持续的动画循环。
- 更新场景的旋转和相机控制。
- 渲染场景。
各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!