Skip to content

ChuXuanYi9/jack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 

Repository files navigation

<title>双面图片360度旋转展示工具</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
    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">

双面图片360度旋转展示工具

上传、自定义并生成动态旋转效果

    <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度旋转展示工具 &copy; 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>

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors