react-3d-carousel-fullcontrol 1.1.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 +9 -44
package/package.json
CHANGED
package/src/Carousel.jsx
CHANGED
|
@@ -174,7 +174,7 @@ const CarouselCard = memo(function CarouselCard({
|
|
|
174
174
|
style={{
|
|
175
175
|
width: "100%",
|
|
176
176
|
height: "100%",
|
|
177
|
-
borderRadius: "
|
|
177
|
+
borderRadius: "12px",
|
|
178
178
|
overflow: "hidden",
|
|
179
179
|
position: "relative",
|
|
180
180
|
cursor: "pointer",
|
|
@@ -194,7 +194,7 @@ const CarouselCard = memo(function CarouselCard({
|
|
|
194
194
|
height: "100%",
|
|
195
195
|
objectFit: "cover",
|
|
196
196
|
display: "block",
|
|
197
|
-
borderRadius: "
|
|
197
|
+
borderRadius: "12px",
|
|
198
198
|
filter: hovered
|
|
199
199
|
? "brightness(1.2) saturate(1.3) contrast(1.1)"
|
|
200
200
|
: `brightness(${0.65 + frontness * 0.3}) saturate(1.05)`,
|
|
@@ -261,7 +261,7 @@ const CarouselCard = memo(function CarouselCard({
|
|
|
261
261
|
right: "5%",
|
|
262
262
|
height: "80px",
|
|
263
263
|
background: `url(${image.src}) center/cover no-repeat`,
|
|
264
|
-
borderRadius: "
|
|
264
|
+
borderRadius: "12px",
|
|
265
265
|
transform: "scaleY(-1)",
|
|
266
266
|
opacity: 0.12 + frontness * 0.08,
|
|
267
267
|
maskImage: "linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 100%)",
|
|
@@ -276,28 +276,6 @@ const CarouselCard = memo(function CarouselCard({
|
|
|
276
276
|
|
|
277
277
|
// ─── Main reusable carousel component ────────────────────────────────────────
|
|
278
278
|
|
|
279
|
-
/**
|
|
280
|
-
* 3D Carousel with cylinder-style rotation and full axis control
|
|
281
|
-
* Cards arranged in a circle and the entire cylinder rotates
|
|
282
|
-
*
|
|
283
|
-
* @param {Object} props
|
|
284
|
-
* @param {Array<{src: string, label?: string}>} props.images
|
|
285
|
-
* @param {Object} [props.defaultRotation] - Default rotation { x: -20, y: 10, z: 20 }
|
|
286
|
-
* @param {boolean} [props.controlled=false] - Return to default position on release
|
|
287
|
-
* @param {function} [props.onRotationChange] - Callback with current rotation
|
|
288
|
-
* @param {number} [props.autoRotateSpeed=0.3] - Auto rotation speed (degrees per frame)
|
|
289
|
-
* @param {Object} [props.autoRotateAxes] - Which axes to auto-rotate { x: false, y: true, z: false }
|
|
290
|
-
* @param {boolean} [props.pauseOnDrag=true] - Pause auto-rotation while dragging
|
|
291
|
-
* @param {number} [props.cardWidth=220]
|
|
292
|
-
* @param {number} [props.cardHeight=300]
|
|
293
|
-
* @param {number} [props.gap=2]
|
|
294
|
-
* @param {number} [props.perspective=2000]
|
|
295
|
-
* @param {number} [props.sensitivity=0.4] - Mouse drag sensitivity
|
|
296
|
-
* @param {boolean} [props.showDragHint=true]
|
|
297
|
-
* @param {boolean} [props.glowEffect=true] - Enable glow effect on hover
|
|
298
|
-
* @param {string} [props.className=""]
|
|
299
|
-
* @param {Object} [props.style={}]
|
|
300
|
-
*/
|
|
301
279
|
function Carousel({
|
|
302
280
|
images = [],
|
|
303
281
|
defaultRotation = { x: -20, y: 10, z: 20 },
|
|
@@ -312,7 +290,6 @@ function Carousel({
|
|
|
312
290
|
perspective = 2000,
|
|
313
291
|
sensitivity = 0.4,
|
|
314
292
|
showDragHint = true,
|
|
315
|
-
glowEffect = true,
|
|
316
293
|
className = "",
|
|
317
294
|
style = {},
|
|
318
295
|
}) {
|
|
@@ -333,7 +310,6 @@ function Carousel({
|
|
|
333
310
|
const angleStep = cardCount > 0 ? 360 / cardCount : 0;
|
|
334
311
|
const radius = cardCount > 0 ? ((cardWidth + gap) * cardCount) / (2 * Math.PI) : 0;
|
|
335
312
|
|
|
336
|
-
// Sync with defaultRotation prop changes
|
|
337
313
|
useEffect(() => {
|
|
338
314
|
if (!isDragging.current) {
|
|
339
315
|
rotationRef.current = {
|
|
@@ -345,7 +321,6 @@ function Carousel({
|
|
|
345
321
|
}
|
|
346
322
|
}, [defaultRotation.x, defaultRotation.y, defaultRotation.z]);
|
|
347
323
|
|
|
348
|
-
// Animation loop
|
|
349
324
|
useEffect(() => {
|
|
350
325
|
if (cardCount === 0) return;
|
|
351
326
|
|
|
@@ -358,7 +333,6 @@ function Carousel({
|
|
|
358
333
|
const isCurrentlyDragging = isDragging.current;
|
|
359
334
|
|
|
360
335
|
if (!isCurrentlyDragging) {
|
|
361
|
-
// Apply friction to all velocities
|
|
362
336
|
velocityRef.current.x *= 0.94;
|
|
363
337
|
velocityRef.current.y *= 0.94;
|
|
364
338
|
velocityRef.current.z *= 0.94;
|
|
@@ -368,22 +342,19 @@ function Carousel({
|
|
|
368
342
|
if (Math.abs(velocityRef.current.z) < 0.001) velocityRef.current.z = 0;
|
|
369
343
|
|
|
370
344
|
if (!controlled) {
|
|
371
|
-
//
|
|
345
|
+
// Auto-rotate on specified axes
|
|
372
346
|
if (autoRotateAxes.x) rotationRef.current.x += autoRotateSpeed * (delta / 16.67);
|
|
373
347
|
if (autoRotateAxes.y) rotationRef.current.y += autoRotateSpeed * (delta / 16.67);
|
|
374
348
|
if (autoRotateAxes.z) rotationRef.current.z += autoRotateSpeed * (delta / 16.67);
|
|
375
349
|
|
|
376
|
-
// Add momentum
|
|
377
350
|
rotationRef.current.x += velocityRef.current.x;
|
|
378
351
|
rotationRef.current.y += velocityRef.current.y;
|
|
379
352
|
rotationRef.current.z += velocityRef.current.z;
|
|
380
353
|
} else {
|
|
381
|
-
// Controlled mode: apply momentum and return to default
|
|
382
354
|
rotationRef.current.x += velocityRef.current.x;
|
|
383
355
|
rotationRef.current.y += velocityRef.current.y;
|
|
384
356
|
rotationRef.current.z += velocityRef.current.z;
|
|
385
357
|
|
|
386
|
-
// If velocities are near zero, return to default position
|
|
387
358
|
if (Math.abs(velocityRef.current.x) < 0.01 &&
|
|
388
359
|
Math.abs(velocityRef.current.y) < 0.01 &&
|
|
389
360
|
Math.abs(velocityRef.current.z) < 0.01) {
|
|
@@ -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 };
|
|
@@ -437,21 +407,20 @@ function Carousel({
|
|
|
437
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 the cylinder
|
|
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 }}>
|
|
@@ -537,7 +503,7 @@ 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",
|
|
@@ -548,7 +514,7 @@ function Carousel({
|
|
|
548
514
|
willChange: "transform",
|
|
549
515
|
}}
|
|
550
516
|
>
|
|
551
|
-
{/*
|
|
517
|
+
{/* INNER CONTAINER: Only spins the cards around Y axis */}
|
|
552
518
|
<div
|
|
553
519
|
style={{
|
|
554
520
|
position: "absolute",
|
|
@@ -559,7 +525,6 @@ function Carousel({
|
|
|
559
525
|
willChange: "transform",
|
|
560
526
|
}}
|
|
561
527
|
>
|
|
562
|
-
{/* Render cards in sorted order (back to front) */}
|
|
563
528
|
{sortedImages.map(({ image, index, zDepth }) => (
|
|
564
529
|
<CarouselCard
|
|
565
530
|
key={index}
|