<div class="tip">Use your finger to swipe it up and down :)</div>
<div class="swiper"></div>
<div class="camera">
  <div class="d-carousel">
    <!-- 图片可任意增减,无需调节其他参数 -->
    <img src="https://picsum.photos/id/1/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/2/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/3/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/4/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/5/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/6/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/7/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/8/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/9/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/10/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/11/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/12/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/13/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/14/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/15/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/16/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/17/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/18/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/19/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/20/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/21/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/22/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/23/200/300" alt="" class="d-carousel-item">
    <img src="https://picsum.photos/id/24/200/300" alt="" class="d-carousel-item">
  </div>
</div>
body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: hsl(240, 56%, 98%);
  margin: 0;
}

.swiper {
  position: absolute;
  width: 120px;
  height: 468px;
  cursor: pointer;
}

.camera {
  $d-carousel-item-width: 45px;
  $d-carousel-item-height: 45px;
  --d-carousel-item-gap: 5px;
  --d-carousel-transition: 0.3s;
  --d-carousel-perspective: 250px;
  --d-carousel-rotate-x: 0;
  --d-carousel-translate-z: -100px;
  --d-carousel-item-width: #{$d-carousel-item-width};
  --d-carousel-item-height: #{$d-carousel-item-height};
  --d-carousel-item-gap-double: calc(var(--d-carousel-item-gap) * 2);
  --camera-width: calc(
    var(--d-carousel-item-width) + var(--d-carousel-item-gap-double)
  );
  --camera-height: calc(
    var(--d-carousel-item-height) + var(--d-carousel-item-gap-double)
  );

  position: relative;
  width: var(--camera-width);
  height: var(--camera-height);
  perspective: var(--d-carousel-perspective);
  pointer-events: none;

  .d-carousel {
    position: absolute;
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    transform: translateZ(var(--d-carousel-translate-z)) rotateX(var(--d-carousel-rotate-x));
    transition: var(--d-carousel-transition);
    pointer-events: none;

    &-item {
      --d-carousel-item-rotate-x: calc(var(--i) * var(--d-carousel-item-deg));
      --d-carousel-item-translate-z: var(--d-carousel-item-r);

      position: absolute;
      left: var(--d-carousel-item-gap);
      top: var(--d-carousel-item-gap);
      width: var(--d-carousel-item-width);
      height: var(--d-carousel-item-height);
      border-radius: 5px;
      backface-visibility: hidden;
      pointer-events: none;

      transform: rotateX(var(--d-carousel-item-rotate-x))
        translateZ(var(--d-carousel-item-translate-z));
    }
  }
}

.tip {
  position: absolute;
  top: 4rem;
}
View Compiled
const getTanFromDegrees = (deg: number) => Math.tan((deg * Math.PI) / 180);
const camera = document.querySelector(".camera") as HTMLElement;
const cameraHeight = camera.offsetHeight;
const swiper = document.querySelector(".swiper");
const swiperHeight = (swiper as HTMLElement).offsetHeight;
const dCarousel = document.querySelector(".d-carousel") as HTMLElement;
const dCarouselItems = dCarousel.children;
const dCarouselItemCount = dCarouselItems.length;
const dCarouselItemDeg = 360 / dCarouselItemCount;
const dCarouselItemTanDegHalf = Math.tan(
  getTanFromDegrees(dCarouselItemDeg / 2)
);
const dCarouselItemR = cameraHeight / 2 / dCarouselItemTanDegHalf;
dCarousel.style.setProperty("--d-carousel-item-r", `${dCarouselItemR}px`);
dCarousel.style.setProperty("--d-carousel-item-deg", `${dCarouselItemDeg}deg`);
Array.from(dCarouselItems).forEach((item, i) =>
  (item as HTMLElement).style.setProperty("--i", `${i}`)
);
let swipeDeg = 0;
let previousDeltaY = 0;
let deltaYDelta = 0;
const manager = new Hammer.Manager(swiper);
const Pan = new Hammer.Pan({ threshold: 10 });
manager.add(Pan);
manager.on("pan", (e) => {
  const deltaY = e.deltaY;
  deltaYDelta = deltaY - previousDeltaY;
  previousDeltaY = deltaY;
  const direction = e.offsetDirection;
  if (direction === 8 || direction === 16) {
    const swipeDegDelta = (deltaYDelta / swiperHeight) * 360;
    swipeDeg -= swipeDegDelta;
    dCarousel.style.setProperty("--d-carousel-rotate-x", `${swipeDeg}deg`);
  }
  if (e.isFinal) {
    previousDeltaY = 0;
  }
});
const disableTouchMove = () => {
  document.body.addEventListener(
    "touchmove",
    (e) => {
      e.preventDefault();
    },
    { passive: false }
  );
};
disableTouchMove();
View Compiled

External CSS

  1. https://cdn.jsdelivr.net/npm/@alphardex/aqua.css@1.5.0/dist/aqua.min.css

External JavaScript

  1. https://cdn.bootcdn.net/ajax/libs/hammer.js/2.0.8/hammer.min.js