Forked from tedbow/gist:4d829fff18e99c1fd72f7ec7ee8ca9ec
Created
April 20, 2026 15:42
-
-
Save wimleers/b7ba929643b57c844a2f28a2ee5c5b41 to your computer and use it in GitHub Desktop.
Images Go Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { h } from 'preact'; | |
| import { useState, useEffect, useRef } from 'preact/hooks'; | |
| const ErraticGallery = ({ images = [] }) => { | |
| const containerRef = useRef(null); | |
| const [items, setItems] = useState([]); | |
| useEffect(() => { | |
| if (!images || !Array.isArray(images)) return; | |
| const initialItems = images.map((item) => { | |
| // Extract the URL safely from the Drupal Object | |
| let src = ""; | |
| if (typeof item === 'string') { | |
| src = item; | |
| } else if (item && typeof item === 'object') { | |
| src = item.url || item.uri || item.src || ""; | |
| } | |
| return { | |
| src: src || "https://placehold.co/400x400?text=Missing+URL", | |
| x: Math.random() * 50, | |
| y: Math.random() * 50, | |
| vx: (Math.random() - 0.5) * 12, | |
| vy: (Math.random() - 0.5) * 12, | |
| rotation: 0, | |
| vr: (Math.random() - 0.5) * 15 | |
| }; | |
| }); | |
| setItems(initialItems); | |
| }, [images]); | |
| useEffect(() => { | |
| if (items.length === 0) return; | |
| let frameId; | |
| const update = () => { | |
| setItems((prev) => | |
| prev.map((item) => { | |
| const container = containerRef.current; | |
| if (!container) return item; | |
| const w = container.offsetWidth; | |
| const h = container.offsetHeight; | |
| const size = 160; // 2x larger than before | |
| let { x, y, vx, vy, rotation, vr } = item; | |
| x += vx; y += vy; rotation += vr; | |
| // Bounce logic with speed-up multiplier | |
| if (x <= 0 || x >= w - size) { | |
| vx *= -1.02; // Get faster on every bounce | |
| vr = (Math.random() - 0.5) * 30; // Randomize spin on hit | |
| } | |
| if (y <= 0 || y >= h - size) { | |
| vy *= -1.02; | |
| vr = (Math.random() - 0.5) * 30; | |
| } | |
| // Speed limit to keep it from teleporting | |
| vx = Math.min(Math.max(vx, -25), 25); | |
| vy = Math.min(Math.max(vy, -25), 25); | |
| return { ...item, x, y, vx, vy, rotation }; | |
| }) | |
| ); | |
| frameId = requestAnimationFrame(update); | |
| }; | |
| frameId = requestAnimationFrame(update); | |
| return () => cancelAnimationFrame(frameId); | |
| }, [items.length]); | |
| return ( | |
| <div | |
| ref={containerRef} | |
| style={{ | |
| width: '100%', | |
| height: '600px', | |
| position: 'relative', | |
| overflow: 'hidden', | |
| background: '#111', | |
| borderRadius: '12px', | |
| border: '5px solid #5419ad' // Drupal purple | |
| }} | |
| > | |
| {items.map((item, i) => ( | |
| <img | |
| key={i} | |
| src={item.src} | |
| style={{ | |
| position: 'absolute', | |
| left: `${item.x}px`, | |
| top: `${item.y}px`, | |
| width: '400px', // 2x larger | |
| height: '400px', // 2x larger | |
| objectFit: 'cover', | |
| borderRadius: '50%', | |
| border: '4px solid #fff', | |
| transform: `rotate(${item.rotation}deg)`, | |
| boxShadow: '0 0 40px rgba(255,255,255,0.3)', | |
| pointerEvents: 'none' | |
| }} | |
| onError={(e) => { | |
| // If Drupal path is relative, try to fix it automatically | |
| if (!item.src.startsWith('http') && !item.src.startsWith('data:')) { | |
| e.target.src = window.location.origin + item.src; | |
| } | |
| }} | |
| /> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| export default ErraticGallery; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment