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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/Carousel.jsx +34 -56
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-3d-carousel-fullcontrol",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "A highly customizable 3D carousel component for React with full axis rotation control",
5
5
  "main": "src/Carousel.jsx",
6
6
  "keywords": [
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
- // Free mode: apply auto-rotation to all specified axes
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 around)
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 forward/backward)
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/twist)
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 rotations to reasonable ranges
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
- {/* Apply all 3 axis rotations */}
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) rotateY(${renderRotation.y}deg) rotateZ(${renderRotation.z}deg)`,
513
+ transform: `translate(-50%, -50%) rotateX(${renderRotation.x}deg) rotateZ(${renderRotation.z}deg)`,
548
514
  willChange: "transform",
549
515
  }}
550
516
  >
551
- {sortedImages.map(({ image, index, zDepth }) => (
552
- <CarouselCard
553
- key={index}
554
- image={image}
555
- index={index}
556
- totalAngle={renderRotation.y}
557
- opacity={0.3 + (zDepth + 1) * 0.35}
558
- angleStep={angleStep}
559
- radius={radius}
560
- cardWidth={cardWidth}
561
- cardHeight={cardHeight}
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 && (