body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: white;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
h1 {
font-size: 2.8rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
}
.main-content {
display: flex;
flex-wrap: wrap;
gap: 30px;
margin-bottom: 30px;
}
.display-section {
flex: 1;
min-width: 500px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.controls-section {
flex: 1;
min-width: 300px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
#canvas-container {
width: 100%;
height: 400px;
background: #1a1a2e;
border-radius: 10px;
overflow: hidden;
margin-bottom: 20px;
position: relative;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
.control-group {
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.control-group:last-child {
border-bottom: none;
}
h2 {
font-size: 1.5rem;
margin-bottom: 15px;
color: #ffcc00;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 15px;
}
button {
padding: 10px 15px;
background: #4a4af0;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
font-weight: 600;
display: flex;
align-items: center;
gap: 5px;
}
button:hover {
background: #3a3ad0;
transform: translateY(-2px);
}
button.active {
background: #ffcc00;
color: #333;
}
button i {
font-size: 1.1rem;
}
input[type="range"] {
width: 100%;
margin: 10px 0;
}
input[type="color"] {
width: 60px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
}
input[type="file"] {
display: none;
}
.file-upload {
display: inline-block;
padding: 10px 15px;
background: #4aaf4a;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 10px;
}
.file-upload:hover {
background: #3a9f3a;
}
.preview-images {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.preview-img {
width: 80px;
height: 60px;
object-fit: cover;
border-radius: 5px;
cursor: pointer;
opacity: 0.7;
transition: all 0.3s;
border: 2px solid transparent;
}
.preview-img:hover, .preview-img.active {
opacity: 1;
border-color: #ffcc00;
transform: scale(1.05);
}
.status {
margin-top: 15px;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
border-radius: 5px;
font-size: 0.9rem;
}
.gif-controls {
display: flex;
flex-direction: column;
gap: 10px;
}
.gif-preview {
margin-top: 15px;
text-align: center;
}
.gif-preview img {
max-width: 100%;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.hidden {
display: none;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 15px 25px;
border-radius: 10px;
z-index: 100;
}
footer {
text-align: center;
padding: 20px;
font-size: 0.9rem;
opacity: 0.8;
}
@media (max-width: 768px) {
.main-content {
flex-direction: column;
}
.display-section, .controls-section {
min-width: 100%;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
上传、自定义并生成动态旋转效果
<div class="main-content">
<section class="display-section">
<h2><i class="fas fa-image"></i> 图片展示区</h2>
<div id="canvas-container">
<canvas id="renderCanvas"></canvas>
<div id="loading" class="loading hidden">
<i class="fas fa-spinner fa-spin"></i> 加载中...
</div>
</div>
<div class="status">
<p>状态: <span id="status-text">准备就绪</span></p>
<p>旋转速度: <span id="speed-value">0.01</span></p>
</div>
</section>
<section class="controls-section">
<div class="control-group">
<h2><i class="fas fa-sliders-h"></i> 旋转控制</h2>
<div class="button-group">
<button id="start-rotate"><i class="fas fa-play"></i> 开始旋转</button>
<button id="stop-rotate"><i class="fas fa-stop"></i> 停止旋转</button>
<button id="reset-view"><i class="fas fa-sync-alt"></i> 重置视角</button>
</div>
<label>旋转速度: <span id="speed-display">0.01</span></label>
<input type="range" id="rotation-speed" min="0.005" max="0.05" step="0.005" value="0.01">
</div>
<div class="control-group">
<h2><i class="fas fa-palette"></i> 背景设置</h2>
<div class="button-group">
<button id="bg-color-btn" class="active"><i class="fas fa-fill-drip"></i> 颜色背景</button>
<button id="bg-image-btn"><i class="fas fa-image"></i> 图片背景</button>
</div>
<div id="bg-color-controls">
<label>选择背景颜色:</label>
<input type="color" id="bg-color" value="#1a1a2e">
</div>
<div id="bg-image-controls" class="hidden">
<label class="file-upload">
<i class="fas fa-upload"></i> 上传背景图片
<input type="file" id="bg-image-input" accept="image/*">
</label>
<div id="bg-preview-container" class="preview-images"></div>
</div>
</div>
<div class="control-group">
<h2><i class="fas fa-exchange-alt"></i> 图片管理</h2>
<label class="file-upload">
<i class="fas fa-upload"></i> 上传展示图片
<input type="file" id="image-input" accept="image/*" multiple>
</label>
<div class="preview-images" id="image-previews">
<!-- 预览图片将在这里动态添加 -->
</div>
</div>
<div class="control-group">
<h2><i class="fas fa-film"></i> 生成动态图</h2>
<div class="gif-controls">
<label>帧数: <span id="frame-count">20</span></label>
<input type="range" id="frame-count-slider" min="5" max="30" step="1" value="20">
<label>持续时间(秒): <span id="duration-value">2</span></label>
<input type="range" id="duration-slider" min="1" max="5" step="0.5" value="2">
<button id="generate-gif"><i class="fas fa-video"></i> 生成动态图序列</button>
</div>
<div id="gif-preview" class="gif-preview hidden">
<h3>动态图预览</h3>
<img id="gif-result" src="" alt="动态图预览">
<button id="download-gif"><i class="fas fa-download"></i> 下载动态图</button>
</div>
</div>
</section>
</div>
<footer>
<p>双面图片360度旋转展示工具 © 2023 | 使用Three.js构建</p>
</footer>
</div>
<!-- 引入Three.js库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<!-- 引入GIF生成库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.min.js"></script>
<script>
// 初始化Three.js场景
let scene, camera, renderer, controls;
let frontPlane, backPlane;
let isRotating = false;
let rotationSpeed = 0.01;
let currentImageIndex = 0;
let images = [];
let backgroundScene = null;
// 初始化函数
function init() {
// 创建场景
scene = new THREE.Scene();
// 创建相机
camera = new THREE.PerspectiveCamera(75,
document.getElementById('canvas-container').clientWidth /
document.getElementById('canvas-container').clientHeight, 0.1, 1000);
camera.position.z = 7;
camera.position.y = 2;
// 创建渲染器
const canvas = document.getElementById('renderCanvas');
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setSize(
document.getElementById('canvas-container').clientWidth,
document.getElementById('canvas-container').clientHeight
);
renderer.setClearColor(0x000000, 0);
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight1.position.set(5, 5, 5);
scene.add(directionalLight1);
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
directionalLight2.position.set(-5, -5, -5);
scene.add(directionalLight2);
// 添加轨道控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = true;
controls.minDistance = 3;
controls.maxDistance = 20;
// 创建默认双面平面
createDefaultPlanes();
// 设置背景颜色
updateBackground();
// 开始动画循环
animate();
// 添加窗口调整大小事件监听
window.addEventListener('resize', onWindowResize);
}
// 创建默认双面平面
function createDefaultPlanes() {
// 创建正面平面
const frontGeometry = new THREE.PlaneGeometry(6, 4);
const frontMaterial = new THREE.MeshLambertMaterial({
color: 0xdddddd,
side: THREE.FrontSide,
transparent: true
});
frontPlane = new THREE.Mesh(frontGeometry, frontMaterial);
frontPlane.position.z = 0.01;
// 创建背面平面
const backGeometry = new THREE.PlaneGeometry(6, 4);
const backMaterial = new THREE.MeshLambertMaterial({
color: 0xdddddd,
side: THREE.FrontSide,
transparent: true
});
backPlane = new THREE.Mesh(backGeometry, backMaterial);
backPlane.rotation.y = Math.PI;
backPlane.position.z = -0.01;
scene.add(frontPlane);
scene.add(backPlane);
}
// 更新纹理
function updateTexture(imageUrl) {
const textureLoader = new THREE.TextureLoader();
textureLoader.load(imageUrl, function(texture) {
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
// 移除旧平面
if (frontPlane) scene.remove(frontPlane);
if (backPlane) scene.remove(backPlane);
// 创建新正面平面
const frontGeometry = new THREE.PlaneGeometry(6, 4);
const frontMaterial = new THREE.MeshLambertMaterial({
map: texture,
side: THREE.FrontSide,
transparent: true
});
frontPlane = new THREE.Mesh(frontGeometry, frontMaterial);
frontPlane.position.z = 0.01;
// 创建新背面平面
const backGeometry = new THREE.PlaneGeometry(6, 4);
const backMaterial = new THREE.MeshLambertMaterial({
map: texture,
side: THREE.FrontSide,
transparent: true
});
backPlane = new THREE.Mesh(backGeometry, backMaterial);
backPlane.rotation.y = Math.PI;
backPlane.position.z = -0.01;
scene.add(frontPlane);
scene.add(backPlane);
updateStatus("图片加载成功");
}, undefined, function(error) {
updateStatus("图片加载失败: " + error, true);
});
}
// 更新背景
function updateBackground() {
const bgType = document.getElementById('bg-color-btn').classList.contains('active') ? 'color' : 'image';
if (bgType === 'color') {
const color = document.getElementById('bg-color').value;
renderer.setClearColor(new THREE.Color(color), 1);
// 移除背景场景
if (backgroundScene) {
scene.background = null;
backgroundScene = null;
}
} else {
// 如果已经设置了背景图片,则使用它
if (backgroundScene) {
scene.background = backgroundScene;
} else {
// 否则使用默认颜色
renderer.setClearColor(new THREE.Color('#1a1a2e'), 1);
}
}
}
// 设置背景图片
function setBackgroundImage(imageUrl) {
const textureLoader = new THREE.TextureLoader();
textureLoader.load(imageUrl, function(texture) {
backgroundScene = texture;
scene.background = backgroundScene;
updateStatus("背景图片加载成功");
}, undefined, function(error) {
updateStatus("背景图片加载失败: " + error, true);
});
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
if (isRotating && frontPlane && backPlane) {
frontPlane.rotation.y += rotationSpeed;
backPlane.rotation.y += rotationSpeed;
}
controls.update();
renderer.render(scene, camera);
}
// 窗口大小调整处理
function onWindowResize() {
camera.aspect = document.getElementById('canvas-container').clientWidth /
document.getElementById('canvas-container').clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(
document.getElementById('canvas-container').clientWidth,
document.getElementById('canvas-container').clientHeight
);
}
// 更新状态显示
function updateStatus(message, isError = false) {
const statusElement = document.getElementById('status-text');
statusElement.textContent = message;
statusElement.style.color = isError ? '#ff6b6b' : '#4ecdc4';
}
// 生成动态图
function generateGIF() {
updateStatus("正在生成动态图...");
const frameCount = parseInt(document.getElementById('frame-count').textContent);
const duration = parseFloat(document.getElementById('duration-value').textContent);
const frameDelay = (duration * 1000) / frameCount;
const gif = new GIF({
workers: 2,
quality: 10,
width: 400,
height: 300
});
// 保存原始状态
const originalRotationY = frontPlane.rotation.y;
const originalAutoRotate = isRotating;
isRotating = false;
// 捕获每一帧
for (let i = 0; i < frameCount; i++) {
// 设置旋转角度
frontPlane.rotation.y = originalRotationY + (i / frameCount) * Math.PI * 2;
backPlane.rotation.y = Math.PI + originalRotationY + (i / frameCount) * Math.PI * 2;
// 渲染场景
renderer.render(scene, camera);
// 将canvas添加到GIF
gif.addFrame(renderer.domElement, {delay: frameDelay, copy: true});
}
// 恢复原始状态
frontPlane.rotation.y = originalRotationY;
backPlane.rotation.y = Math.PI + originalRotationY;
isRotating = originalAutoRotate;
// 生成GIF
gif.on('finished', function(blob) {
const gifUrl = URL.createObjectURL(blob);
document.getElementById('gif-result').src = gifUrl;
document.getElementById('gif-preview').classList.remove('hidden');
// 设置下载链接
document.getElementById('download-gif').onclick = function() {
const a = document.createElement('a');
a.href = gifUrl;
a.download = 'rotating_image.gif';
a.click();
};
updateStatus("动态图生成完成");
});
gif.render();
}
// 事件监听器设置
document.addEventListener('DOMContentLoaded', function() {
// 初始化Three.js场景
init();
// 旋转控制
document.getElementById('start-rotate').addEventListener('click', function() {
isRotating = true;
updateStatus("旋转中");
this.classList.add('active');
document.getElementById('stop-rotate').classList.remove('active');
});
document.getElementById('stop-rotate').addEventListener('click', function() {
isRotating = false;
updateStatus("已停止");
this.classList.add('active');
document.getElementById('start-rotate').classList.remove('active');
});
document.getElementById('reset-view').addEventListener('click', function() {
camera.position.set(0, 0, 7);
controls.reset();
if (frontPlane && backPlane) {
frontPlane.rotation.set(0, 0, 0);
backPlane.rotation.set(0, Math.PI, 0);
}
updateStatus("视角已重置");
});
// 旋转速度控制
document.getElementById('rotation-speed').addEventListener('input', function() {
rotationSpeed = parseFloat(this.value);
document.getElementById('speed-value').textContent = rotationSpeed.toFixed(3);
document.getElementById('speed-display').textContent = rotationSpeed.toFixed(3);
});
// 背景类型切换
document.getElementById('bg-color-btn').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('bg-image-btn').classList.remove('active');
document.getElementById('bg-color-controls').classList.remove('hidden');
document.getElementById('bg-image-controls').classList.add('hidden');
updateBackground();
});
document.getElementById('bg-image-btn').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('bg-color-btn').classList.remove('active');
document.getElementById('bg-image-controls').classList.remove('hidden');
document.getElementById('bg-color-controls').classList.add('hidden');
updateBackground();
});
// 背景颜色更改
document.getElementById('bg-color').addEventListener('input', function() {
updateBackground();
});
// 背景图片上传
document.getElementById('bg-image-input').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
document.getElementById('loading').classList.remove('hidden');
updateStatus("正在加载背景图片...");
const reader = new FileReader();
reader.onload = function(event) {
setBackgroundImage(event.target.result);
// 创建预览
const previewContainer = document.getElementById('bg-preview-container');
previewContainer.innerHTML = '';
const img = document.createElement('img');
img.src = event.target.result;
img.className = 'preview-img';
img.style.width = '120px';
img.style.height = '80px';
previewContainer.appendChild(img);
document.getElementById('loading').classList.add('hidden');
};
reader.readAsDataURL(file);
});
// 展示图片上传
document.getElementById('image-input').addEventListener('change', function(e) {
const files = e.target.files;
if (!files || files.length === 0) return;
document.getElementById('loading').classList.remove('hidden');
updateStatus("正在加载图片...");
images = [];
const previewContainer = document.getElementById('image-previews');
previewContainer.innerHTML = '';
Array.from(files).forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(event) {
images.push(event.target.result);
// 创建预览图
const img = document.createElement('img');
img.src = event.target.result;
img.className = 'preview-img';
if (index === 0) img.classList.add('active');
img.addEventListener('click', function() {
document.querySelectorAll('.preview-img').forEach(el => {
el.classList.remove('active');
});
this.classList.add('active');
currentImageIndex = index;
updateTexture(images[index]);
});
previewContainer.appendChild(img);
// 如果是第一张图片,设置为当前展示图片
if (index === 0) {
updateTexture(event.target.result);
}
if (index === files.length - 1) {
document.getElementById('loading').classList.add('hidden');
updateStatus(`已加载 ${files.length} 张图片`);
}
};
reader.readAsDataURL(file);
});
});
// 动态图帧数控制
document.getElementById('frame-count-slider').addEventListener('input', function() {
document.getElementById('frame-count').textContent = this.value;
});
// 动态图持续时间控制
document.getElementById('duration-slider').addEventListener('input', function() {
document.getElementById('duration-value').textContent = this.value;
});
// 生成动态图
document.getElementById('generate-gif').addEventListener('click', function() {
generateGIF();
});
});
</script>