| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>3D Card Gallery</title> |
| <style> |
| body, html { |
| margin: 0; |
| padding: 0; |
| width: 100%; |
| height: 100%; |
| overflow: hidden; |
| background: #000; |
| } |
| canvas { |
| display: block; |
| } |
| .loading { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| color: white; |
| font-family: Arial, sans-serif; |
| font-size: 24px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="loading">Loading...</div> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/18.6.4/tween.umd.js"></script> |
| <script> |
| let scene, camera, renderer, cards = []; |
| const images = [ |
| 'https://picsum.photos/512/512?random=1', |
| 'https://picsum.photos/512/512?random=2', |
| 'https://picsum.photos/512/512?random=3', |
| 'https://picsum.photos/512/512?random=4', |
| 'https://picsum.photos/512/512?random=5', |
| 'https://picsum.photos/512/512?random=6' |
| ]; |
| |
| const textureLoader = new THREE.TextureLoader(); |
| let loadedTextures = 0; |
| |
| function init() { |
| scene = new THREE.Scene(); |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| document.body.appendChild(renderer.domElement); |
| |
| camera.position.z = 10; |
| |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
| scene.add(ambientLight); |
| |
| const pointLight = new THREE.PointLight(0xffffff, 1); |
| pointLight.position.set(10, 10, 10); |
| scene.add(pointLight); |
| |
| images.forEach((url, index) => { |
| textureLoader.load(url, (texture) => { |
| createCard(texture, index); |
| loadedTextures++; |
| if (loadedTextures === images.length) { |
| document.querySelector('.loading').style.display = 'none'; |
| } |
| }); |
| }); |
| |
| document.addEventListener('mousemove', onMouseMove); |
| animate(); |
| } |
| |
| function createCard(texture, index) { |
| const geometry = new THREE.PlaneGeometry(3, 4); |
| const material = new THREE.MeshPhongMaterial({ |
| map: texture, |
| side: THREE.DoubleSide |
| }); |
| const card = new THREE.Mesh(geometry, material); |
| |
| const angle = (index / images.length) * Math.PI * 2; |
| const radius = 6; |
| card.position.x = Math.cos(angle) * radius; |
| card.position.z = Math.sin(angle) * radius; |
| card.rotation.y = -angle; |
| |
| cards.push(card); |
| scene.add(card); |
| } |
| |
| function onMouseMove(event) { |
| const mouseX = (event.clientX / window.innerWidth) * 2 - 1; |
| const mouseY = (event.clientY / window.innerHeight) * 2 - 1; |
| |
| cards.forEach(card => { |
| card.rotation.x = mouseY * 0.2; |
| card.rotation.z = mouseX * 0.2; |
| }); |
| } |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| |
| cards.forEach((card, index) => { |
| const time = Date.now() * 0.001; |
| const angle = (index / images.length) * Math.PI * 2 + time * 0.5; |
| const radius = 6; |
| |
| card.position.x = Math.cos(angle) * radius; |
| card.position.z = Math.sin(angle) * radius; |
| card.rotation.y = -angle; |
| }); |
| |
| TWEEN.update(); |
| renderer.render(scene, camera); |
| } |
| |
| window.addEventListener('resize', () => { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| |
| init(); |
| </script> |
| </body> |
| </html> |