
Flere nybiler på Oslo Motor Show
Etter fjorårets Oslo Motor Show var det rundt 60 prosent som ga tilbakemelding om at de gjerne ville se flere nybiler på messen. Dette har
import { useRef, useEffect, useState, useMemo, useId } from 'react'; import './CurvedLoop.css'; const CurvedLoop = ({ marqueeText = '1', speed = 2, className, curveAmount = 400, direction = 'left', interactive = true }) => { const text = useMemo(() => { const hasTrailing = /\s|\u00A0$/.test(marqueeText); return (hasTrailing ? marqueeText.replace(/\s+$/, '') : marqueeText) + '\u00A0'; }, [marqueeText]); const measureRef = useRef(null); const textPathRef = useRef(null); const pathRef = useRef(null); const [spacing, setSpacing] = useState(0); const [offset, setOffset] = useState(0); const uid = useId(); const pathId = `curve-${uid}`; const pathD = `M-100,40 Q500,${40 + curveAmount} 1540,40`; const dragRef = useRef(false); const lastXRef = useRef(0); const dirRef = useRef(direction); const velRef = useRef(0); const textLength = spacing; const totalText = textLength ? Array(Math.ceil(1800 / textLength) + 2) .fill(text) .join('') : text; const ready = spacing > 0; useEffect(() => { if (measureRef.current) setSpacing(measureRef.current.getComputedTextLength()); }, [text, className]); useEffect(() => { if (!spacing) return; if (textPathRef.current) { const initial = -spacing; textPathRef.current.setAttribute('startOffset', initial + 'px'); setOffset(initial); } }, [spacing]); useEffect(() => { if (!spacing || !ready) return; let frame = 0; const step = () => { if (!dragRef.current && textPathRef.current) { const delta = dirRef.current === 'right' ? speed : -speed; const currentOffset = parseFloat(textPathRef.current.getAttribute('startOffset') || '0'); let newOffset = currentOffset + delta; const wrapPoint = spacing; if (newOffset <= -wrapPoint) newOffset += wrapPoint; if (newOffset > 0) newOffset -= wrapPoint; textPathRef.current.setAttribute('startOffset', newOffset + 'px'); setOffset(newOffset); } frame = requestAnimationFrame(step); }; frame = requestAnimationFrame(step); return () => cancelAnimationFrame(frame); }, [spacing, speed, ready]); const onPointerDown = e => { if (!interactive) return; dragRef.current = true; lastXRef.current = e.clientX; velRef.current = 0; e.target.setPointerCapture(e.pointerId); }; const onPointerMove = e => { if (!interactive || !dragRef.current || !textPathRef.current) return; const dx = e.clientX - lastXRef.current; lastXRef.current = e.clientX; velRef.current = dx; const currentOffset = parseFloat(textPathRef.current.getAttribute('startOffset') || '0'); let newOffset = currentOffset + dx; const wrapPoint = spacing; if (newOffset <= -wrapPoint) newOffset += wrapPoint; if (newOffset > 0) newOffset -= wrapPoint; textPathRef.current.setAttribute('startOffset', newOffset + 'px'); setOffset(newOffset); }; const endDrag = () => { if (!interactive) return; dragRef.current = false; dirRef.current = velRef.current > 0 ? 'right' : 'left'; }; const cursorStyle = interactive ? (dragRef.current ? 'grabbing' : 'grab') : 'auto'; return (