To enhance the Car360View component to support a full spherical 360-degree view (including up, down, left, right, and all directions), we need to account for both horizontal (yaw) and vertical (pitch) rotations. This requires a 2D array of images representing different angles in both axes (e.g., yaw from 0° to 360° and pitch from -90° to 90°). The component will still support dragging (mouse/touch) and arrow buttons, but now for both horizontal and vertical navigation. Below is the updated React component code.
Sample Image Names for Spherical View
const carImages = [
// Pitch -90° (looking straight up)
[
'/images/car_p-90_y000.jpg',
'/images/car_p-90_y010.jpg',
'/images/car_p-90_y020.jpg',
// ... up to '/images/car_p-90_y350.jpg'
],
// Pitch -80°
[
'/images/car_p-80_y000.jpg',
'/images/car_p-80_y010.jpg',
'/images/car_p-80_y020.jpg',
// ... up to '/images/car_p-80_y350.jpg'
],
// ... continue for pitch -70°, -60°, ..., 0° (neutral), ..., 80°
// Pitch 0° (horizontal)
[
'/images/car_p000_y000.jpg',
'/images/car_p000_y010.jpg',
'/images/car_p000_y020.jpg',
// ... up to '/images/car_p000_y350.jpg'
],
// ... continue up to pitch 80°
// Pitch 90° (looking straight down)
[
'/images/car_p090_y000.jpg',
'/images/car_p090_y010.jpg',
'/images/car_p090_y020.jpg',
// ... up to '/images/car_p090_y350.jpg'
],
];
To support all directions (up, down, left, right), the images prop should be a 2D array where images[pitchIndex][yawIndex] corresponds to an image at a specific pitch (vertical angle) and yaw (horizontal angle). Assuming 19 pitch angles (from -90° to 90°, every 10°) and 36 yaw angles (0° to 350°, every 10°), here’s a sample structure:
import React, { useState, useRef } from 'react';
const Car360View = ({ images }) => {
// images is a 2D array: images[pitchIndex][yawIndex]
const [yawIndex, setYawIndex] = useState(0); // Horizontal angle (left-right)
const [pitchIndex, setPitchIndex] = useState(Math.floor(images.length / 2)); // Vertical angle (up-down), start at neutral pitch
const [dragging, setDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [startY, setStartY] = useState(0);
const [startYawIndex, setStartYawIndex] = useState(0);
const [startPitchIndex, setStartPitchIndex] = useState(0);
const containerRef = useRef(null);
const pitchCount = images.length; // Number of vertical angles
const yawCount = images[0]?.length || 1; // Number of horizontal angles per pitch
// Handle arrow clicks
const handleLeft = () => {
setYawIndex((prev) => (prev - 1 + yawCount) % yawCount);
};
const handleRight = () => {
setYawIndex((prev) => (prev + 1) % yawCount);
};
const handleUp = () => {
setPitchIndex((prev) => Math.max(0, prev - 1)); // Prevent going above top
};
const handleDown = () => {
setPitchIndex((prev) => Math.min(pitchCount - 1, prev + 1)); // Prevent going below bottom
};
// Mouse down handler
const handleMouseDown = (e) => {
e.preventDefault();
setDragging(true);
setStartX(e.pageX);
setStartY(e.pageY);
setStartYawIndex(yawIndex);
setStartPitchIndex(pitchIndex);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
// Mouse move handler
const handleMouseMove = (e) => {
if (!dragging) return;
e.preventDefault();
const deltaX = e.pageX - startX;
const deltaY = e.pageY - startY;
const width = containerRef.current?.offsetWidth || 1;
const height = containerRef.current?.offsetHeight || 1;
// Update yaw (horizontal)
const yawOffset = (deltaX / width) * yawCount;
const newYawIndex = Math.round(startYawIndex + yawOffset) % yawCount;
setYawIndex((newYawIndex + yawCount) % yawCount);
// Update pitch (vertical)
const pitchOffset = (deltaY / height) * pitchCount;
const newPitchIndex = Math.round(startPitchIndex - pitchOffset); // Negative because dragging down increases pitch
setPitchIndex(Math.max(0, Math.min(pitchCount - 1, newPitchIndex)));
};
// Mouse up handler
const handleMouseUp = () => {
setDragging(false);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
// Touch start handler
const handleTouchStart = (e) => {
setDragging(true);
setStartX(e.touches[0].pageX);
setStartY(e.touches[0].pageY);
setStartYawIndex(yawIndex);
setStartPitchIndex(pitchIndex);
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd);
};
// Touch move handler
const handleTouchMove = (e) => {
if (!dragging || e.touches.length !== 1) return;
e.preventDefault();
const deltaX = e.touches[0].pageX - startX;
const deltaY = e.touches[0].pageY - startY;
const width = containerRef.current?.offsetWidth || 1;
const height = containerRef.current?.offsetHeight || 1;
// Update yaw (horizontal)
const yawOffset = (deltaX / width) * yawCount;
const newYawIndex = Math.round(startYawIndex + yawOffset) % yawCount;
setYawIndex((newYawIndex + yawCount) % yawCount);
// Update pitch (vertical)
const pitchOffset = (deltaY / height) * pitchCount;
const newPitchIndex = Math.round(startPitchIndex - pitchOffset);
setPitchIndex(Math.max(0, Math.min(pitchCount - 1, newPitchIndex)));
};
// Touch end handler
const handleTouchEnd = () => {
setDragging(false);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
};
return (
<div
ref={containerRef}
style={{
position: 'relative',
width: '100%',
maxWidth: '600px',
margin: '0 auto',
userSelect: 'none',
}}
>
<img
src={images[pitchIndex][yawIndex]}
alt="360 view of car"
style={{ width: '100%', display: 'block' }}
draggable={false}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
<button
onClick={handleLeft}
style={{
position: 'absolute',
left: '10px',
top: '50%',
transform: 'translateY(-50%)',
background: 'rgba(0,0,0,0.5)',
color: 'white',
border: 'none',
padding: '10px',
cursor: 'pointer',
fontSize: '20px',
}}
>
←
</button>
<button
onClick={handleRight}
style={{
position: 'absolute',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
background: 'rgba(0,0,0,0.5)',
color: 'white',
border: 'none',
padding: '10px',
cursor: 'pointer',
fontSize: '20px',
}}
>
→
</button>
<button
onClick={handleUp}
style={{
position: 'absolute',
top: '10px',
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(0,0,0,0.5)',
color: 'white',
border: 'none',
padding: '10px',
cursor: 'pointer',
fontSize: '20px',
}}
>
↑
</button>
<button
onClick={handleDown}
style={{
position: 'absolute',
bottom: '10px',
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(0,0,0,0.5)',
color: 'white',
border: 'none',
padding: '10px',
cursor: 'pointer',
fontSize: '20px',
}}
>
↓
</button>
</div>
);
};
export default Car360View;
