react-3d-carousel-fullcontrol 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Carousel.jsx +34 -56
package/package.json
CHANGED
package/src/Carousel.jsx
CHANGED
|
@@ -180,7 +180,7 @@ const CarouselCard = memo(function CarouselCard({
|
|
|
180
180
|
cursor: "pointer",
|
|
181
181
|
willChange: "transform",
|
|
182
182
|
boxShadow: hovered
|
|
183
|
-
? `0 20px 40px rgba(0,0,0,0.8)`
|
|
183
|
+
? `0 0 30px rgba(160,120,255,0.8), 0 0 60px rgba(100,80,220,0.6), 0 0 100px rgba(60,40,180,0.4), 0 20px 40px rgba(0,0,0,0.8)`
|
|
184
184
|
: `0 10px 20px rgba(0,0,0,0.5)`,
|
|
185
185
|
transition: "box-shadow 0.4s ease",
|
|
186
186
|
}}
|
|
@@ -276,26 +276,6 @@ const CarouselCard = memo(function CarouselCard({
|
|
|
276
276
|
|
|
277
277
|
// ─── Main reusable carousel component ────────────────────────────────────────
|
|
278
278
|
|
|
279
|
-
/**
|
|
280
|
-
* 3D Carousel with full axis rotation control and auto-rotation
|
|
281
|
-
*
|
|
282
|
-
* @param {Object} props
|
|
283
|
-
* @param {Array<{src: string, label?: string}>} props.images
|
|
284
|
-
* @param {Object} [props.defaultRotation] - Default rotation { x: -20, y: 10, z: 20 }
|
|
285
|
-
* @param {boolean} [props.controlled=false] - Return to default position on release
|
|
286
|
-
* @param {function} [props.onRotationChange] - Callback with current rotation
|
|
287
|
-
* @param {number} [props.autoRotateSpeed=0.3] - Auto rotation speed for Y axis (degrees per frame)
|
|
288
|
-
* @param {Object} [props.autoRotateAxes] - Which axes to auto-rotate { x: false, y: true, z: false }
|
|
289
|
-
* @param {boolean} [props.pauseOnDrag=true] - Pause auto-rotation while dragging
|
|
290
|
-
* @param {number} [props.cardWidth=220]
|
|
291
|
-
* @param {number} [props.cardHeight=300]
|
|
292
|
-
* @param {number} [props.gap=2]
|
|
293
|
-
* @param {number} [props.perspective=2000]
|
|
294
|
-
* @param {number} [props.sensitivity=0.4] - Mouse drag sensitivity
|
|
295
|
-
* @param {boolean} [props.showDragHint=true]
|
|
296
|
-
* @param {string} [props.className=""]
|
|
297
|
-
* @param {Object} [props.style={}]
|
|
298
|
-
*/
|
|
299
279
|
function Carousel({
|
|
300
280
|
images = [],
|
|
301
281
|
defaultRotation = { x: -20, y: 10, z: 20 },
|
|
@@ -330,7 +310,6 @@ function Carousel({
|
|
|
330
310
|
const angleStep = cardCount > 0 ? 360 / cardCount : 0;
|
|
331
311
|
const radius = cardCount > 0 ? ((cardWidth + gap) * cardCount) / (2 * Math.PI) : 0;
|
|
332
312
|
|
|
333
|
-
// Sync with defaultRotation prop changes
|
|
334
313
|
useEffect(() => {
|
|
335
314
|
if (!isDragging.current) {
|
|
336
315
|
rotationRef.current = {
|
|
@@ -342,7 +321,6 @@ function Carousel({
|
|
|
342
321
|
}
|
|
343
322
|
}, [defaultRotation.x, defaultRotation.y, defaultRotation.z]);
|
|
344
323
|
|
|
345
|
-
// Animation loop
|
|
346
324
|
useEffect(() => {
|
|
347
325
|
if (cardCount === 0) return;
|
|
348
326
|
|
|
@@ -355,7 +333,6 @@ function Carousel({
|
|
|
355
333
|
const isCurrentlyDragging = isDragging.current;
|
|
356
334
|
|
|
357
335
|
if (!isCurrentlyDragging) {
|
|
358
|
-
// Apply friction to all velocities
|
|
359
336
|
velocityRef.current.x *= 0.94;
|
|
360
337
|
velocityRef.current.y *= 0.94;
|
|
361
338
|
velocityRef.current.z *= 0.94;
|
|
@@ -365,22 +342,19 @@ function Carousel({
|
|
|
365
342
|
if (Math.abs(velocityRef.current.z) < 0.001) velocityRef.current.z = 0;
|
|
366
343
|
|
|
367
344
|
if (!controlled) {
|
|
368
|
-
//
|
|
345
|
+
// Auto-rotate on specified axes
|
|
369
346
|
if (autoRotateAxes.x) rotationRef.current.x += autoRotateSpeed * (delta / 16.67);
|
|
370
347
|
if (autoRotateAxes.y) rotationRef.current.y += autoRotateSpeed * (delta / 16.67);
|
|
371
348
|
if (autoRotateAxes.z) rotationRef.current.z += autoRotateSpeed * (delta / 16.67);
|
|
372
349
|
|
|
373
|
-
// Add momentum
|
|
374
350
|
rotationRef.current.x += velocityRef.current.x;
|
|
375
351
|
rotationRef.current.y += velocityRef.current.y;
|
|
376
352
|
rotationRef.current.z += velocityRef.current.z;
|
|
377
353
|
} else {
|
|
378
|
-
// Controlled mode: apply momentum and return to default
|
|
379
354
|
rotationRef.current.x += velocityRef.current.x;
|
|
380
355
|
rotationRef.current.y += velocityRef.current.y;
|
|
381
356
|
rotationRef.current.z += velocityRef.current.z;
|
|
382
357
|
|
|
383
|
-
// If velocities are near zero, return to default position
|
|
384
358
|
if (Math.abs(velocityRef.current.x) < 0.01 &&
|
|
385
359
|
Math.abs(velocityRef.current.y) < 0.01 &&
|
|
386
360
|
Math.abs(velocityRef.current.z) < 0.01) {
|
|
@@ -395,9 +369,6 @@ function Carousel({
|
|
|
395
369
|
rotationRef.current.z += (targetZ - rotationRef.current.z) * returnSpeed;
|
|
396
370
|
}
|
|
397
371
|
}
|
|
398
|
-
} else if (pauseOnDrag) {
|
|
399
|
-
// During drag: only apply auto-rotation if pauseOnDrag is false
|
|
400
|
-
// (Currently paused, but momentum is handled in pointer move)
|
|
401
372
|
}
|
|
402
373
|
|
|
403
374
|
setRenderRotation({ ...rotationRef.current });
|
|
@@ -416,7 +387,6 @@ function Carousel({
|
|
|
416
387
|
};
|
|
417
388
|
}, [controlled, defaultRotation, autoRotateSpeed, autoRotateAxes, pauseOnDrag, onRotationChange, cardCount]);
|
|
418
389
|
|
|
419
|
-
// Interaction handlers
|
|
420
390
|
const handlePointerDown = useCallback((clientX, clientY) => {
|
|
421
391
|
isDragging.current = true;
|
|
422
392
|
lastMousePos.current = { x: clientX, y: clientY };
|
|
@@ -434,24 +404,23 @@ function Carousel({
|
|
|
434
404
|
const dx = clientX - lastMousePos.current.x;
|
|
435
405
|
const dy = clientY - lastMousePos.current.y;
|
|
436
406
|
|
|
437
|
-
// Horizontal drag = Y axis rotation (spin
|
|
407
|
+
// Horizontal drag = Y axis rotation (spin the cylinder)
|
|
438
408
|
rotationRef.current.y += dx * sensitivity;
|
|
439
409
|
|
|
440
|
-
// Vertical drag = X axis rotation (tilt
|
|
410
|
+
// Vertical drag = X axis rotation (tilt the cylinder)
|
|
441
411
|
rotationRef.current.x -= dy * sensitivity * 0.6;
|
|
442
412
|
|
|
443
|
-
// Diagonal drag = Z axis rotation (roll
|
|
413
|
+
// Diagonal drag = Z axis rotation (roll)
|
|
444
414
|
if (Math.abs(dx) > 2 && Math.abs(dy) > 2) {
|
|
445
415
|
const diagonalComponent = (dx * dy) / Math.sqrt(dx * dx + dy * dy);
|
|
446
416
|
rotationRef.current.z += diagonalComponent * sensitivity * 0.3;
|
|
447
417
|
}
|
|
448
418
|
|
|
449
|
-
// Store velocities for momentum
|
|
450
419
|
velocityRef.current.y = dx * sensitivity * 0.5;
|
|
451
420
|
velocityRef.current.x = -dy * sensitivity * 0.3;
|
|
452
421
|
velocityRef.current.z = (dx * dy / Math.sqrt(dx * dx + dy * dy || 1)) * sensitivity * 0.2;
|
|
453
422
|
|
|
454
|
-
// Clamp
|
|
423
|
+
// Clamp values
|
|
455
424
|
rotationRef.current.x = Math.max(-75, Math.min(75, rotationRef.current.x));
|
|
456
425
|
rotationRef.current.z = Math.max(-45, Math.min(45, rotationRef.current.z));
|
|
457
426
|
|
|
@@ -462,7 +431,6 @@ function Carousel({
|
|
|
462
431
|
isDragging.current = false;
|
|
463
432
|
}, []);
|
|
464
433
|
|
|
465
|
-
// Mouse events
|
|
466
434
|
const onMouseDown = useCallback((e) => {
|
|
467
435
|
e.preventDefault();
|
|
468
436
|
handlePointerDown(e.clientX, e.clientY);
|
|
@@ -476,7 +444,6 @@ function Carousel({
|
|
|
476
444
|
handlePointerUp();
|
|
477
445
|
}, [handlePointerUp]);
|
|
478
446
|
|
|
479
|
-
// Touch events
|
|
480
447
|
const onTouchStart = useCallback((e) => {
|
|
481
448
|
handlePointerDown(e.touches[0].clientX, e.touches[0].clientY);
|
|
482
449
|
}, [handlePointerDown]);
|
|
@@ -489,7 +456,6 @@ function Carousel({
|
|
|
489
456
|
handlePointerUp();
|
|
490
457
|
}, [handlePointerUp]);
|
|
491
458
|
|
|
492
|
-
// Empty state
|
|
493
459
|
if (cardCount === 0) {
|
|
494
460
|
return (
|
|
495
461
|
<div className={className} style={{ position: "relative", width: "100%", height: "100%", ...style }}>
|
|
@@ -507,7 +473,7 @@ function Carousel({
|
|
|
507
473
|
);
|
|
508
474
|
}
|
|
509
475
|
|
|
510
|
-
// Sort cards by depth for proper rendering
|
|
476
|
+
// Sort cards by depth for proper rendering order (back to front)
|
|
511
477
|
const sortedImages = [...images].map((image, i) => {
|
|
512
478
|
const angle = i * angleStep;
|
|
513
479
|
const totalAngle = (renderRotation.y + angle) % 360;
|
|
@@ -537,30 +503,42 @@ function Carousel({
|
|
|
537
503
|
onTouchMove={onTouchMove}
|
|
538
504
|
onTouchEnd={onTouchEnd}
|
|
539
505
|
>
|
|
540
|
-
{/*
|
|
506
|
+
{/* OUTER CONTAINER: Only tilts the view (X and Z rotation) */}
|
|
541
507
|
<div
|
|
542
508
|
style={{
|
|
543
509
|
position: "absolute",
|
|
544
510
|
top: "50%",
|
|
545
511
|
left: "50%",
|
|
546
512
|
transformStyle: "preserve-3d",
|
|
547
|
-
transform: `translate(-50%, -50%) rotateX(${renderRotation.x}deg)
|
|
513
|
+
transform: `translate(-50%, -50%) rotateX(${renderRotation.x}deg) rotateZ(${renderRotation.z}deg)`,
|
|
548
514
|
willChange: "transform",
|
|
549
515
|
}}
|
|
550
516
|
>
|
|
551
|
-
{
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
517
|
+
{/* INNER CONTAINER: Only spins the cards around Y axis */}
|
|
518
|
+
<div
|
|
519
|
+
style={{
|
|
520
|
+
position: "absolute",
|
|
521
|
+
top: "50%",
|
|
522
|
+
left: "50%",
|
|
523
|
+
transformStyle: "preserve-3d",
|
|
524
|
+
transform: `translate(-50%, -50%) rotateY(${renderRotation.y}deg)`,
|
|
525
|
+
willChange: "transform",
|
|
526
|
+
}}
|
|
527
|
+
>
|
|
528
|
+
{sortedImages.map(({ image, index, zDepth }) => (
|
|
529
|
+
<CarouselCard
|
|
530
|
+
key={index}
|
|
531
|
+
image={image}
|
|
532
|
+
index={index}
|
|
533
|
+
totalAngle={renderRotation.y}
|
|
534
|
+
opacity={0.3 + (zDepth + 1) * 0.35}
|
|
535
|
+
angleStep={angleStep}
|
|
536
|
+
radius={radius}
|
|
537
|
+
cardWidth={cardWidth}
|
|
538
|
+
cardHeight={cardHeight}
|
|
539
|
+
/>
|
|
540
|
+
))}
|
|
541
|
+
</div>
|
|
564
542
|
</div>
|
|
565
543
|
|
|
566
544
|
{showDragHint && (
|