uni-app与Vue3,实现3D圆柱形旋转画廊效果

在移动应用开发中,吸引人的视觉效果是提升用户体验的关键因素之一。如何使用uni-app和Vue3实现一个令人惊叹的3D圆柱形旋转画廊效果,这种效果能让你的应用在视觉上脱颖而出。

效果概述

这个3D圆柱形旋转画廊利用CSS 3D变换技术,将图片排列在一个虚拟的圆柱体表面,创造出令人惊叹的3D视觉效果。用户可以通过点击导航按钮或指示器来旋转画廊,查看不同角度的图片,带来沉浸式的浏览体验。

实现原理

1. 3D变换基础

实现这一效果的核心是CSS的3D变换属性。我们通过以下关键属性创建3D空间:

  • perspective: 定义3D元素的透视效果,值越大,3D效果越柔和

  • transform-style: preserve-3d: 确保子元素保持在3D空间中

  • rotateY(): 绕Y轴旋转元素,创建圆柱形排列效果

  • translateZ(): 沿Z轴移动元素,控制元素距离视点的远近

2. 圆柱形排列计算

3. 旋转控制

通过改变容器的rotateY值来实现整体旋转效果

完整代码

TypeScript 复制代码
<template>
  <view class="container">
    <view class="gallery-container">
      <view
        id="carousel"
        class="carousel"
        :style="{ transform: `rotateY(${currentRotation}deg)` }"
      >
        <view
          v-for="(item, index) in items"
          :key="index"
          :class="[
            `carousel-item`,
            index === getClassName ? `carousel-item-active` : '',
          ]"
          :style="getItemStyle(index)"
        >
          <image :src="item.src" mode="aspectFill" class="carousel-image" />
        </view>
      </view>

      <button class="nav-btn prev" @tap="rotateCarousel(rotationStep)">
        <uni-icons type="left" color="#ffffff" size="30"></uni-icons>
      </button>
      <button class="nav-btn next" @tap="rotateCarousel(-rotationStep)">
        <uni-icons type="right" color="#ffffff" size="30"></uni-icons>
      </button>
    </view>

    <view class="gallery-indicators">
      <view
        v-for="(item, index) in items"
        :key="index"
        class="indicator"
        :class="{ active: isActive(index) }"
        @tap="goToItem(index)"
      ></view>
    </view>

    <view class="gallery-info">
      <h2 class="gallery-title">沉浸式3D体验</h2>
      <p class="gallery-desc">
        这个创意画廊利用CSS
        3D变换技术,将图片排列在一个虚拟的圆柱体表面,创造出令人惊叹的3D视觉效果。
        你可以通过点击导航按钮来旋转画廊,查看不同角度的图片。
      </p>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue";

// 图片数据
const items = ref([
  { src: "https://picsum.photos/id/1018/600/800" },
  { src: "https://picsum.photos/id/1015/600/800" },
  { src: "https://picsum.photos/id/1019/600/800" },
  { src: "https://picsum.photos/id/1039/600/800" },
  { src: "https://picsum.photos/id/1043/600/800" },
  { src: "https://picsum.photos/id/1045/600/800" },
]);

const currentRotation = ref(0);
// 增加旋转角度,使每次旋转后只显示三个卡片
const rotationStep = 60;
const totalItems = items.value.length;

// 自动播放计时器
let autoPlayInterval: number | null = null;

// 获取每个项目的样式 - 调整角度和距离使一次只显示三个
const getItemStyle = (index: number) => {
  // 计算每个项目的角度,6个项目每个间隔60度
  const angle = index * (360 / totalItems);
  // 减小Z轴距离,使卡片更靠近中心,不会铺满全屏
  const translateZ = 220;

  return `transform: rotateY(${angle}deg) translateZ(${translateZ}px)`;
};

// 检查指示器是否激活
const isActive = (index: number) => {
  const activeIndex = Math.round((-currentRotation.value % 360) / rotationStep);
  return index === ((activeIndex % totalItems) + totalItems) % totalItems;
};

// 旋转画廊
const rotateCarousel = (step: number) => {
  currentRotation.value += step;
};

// 跳转到指定项目
const goToItem = (index: number) => {
  const targetRotation = index * -rotationStep;
  const diff = targetRotation - currentRotation.value;
  const steps = diff / rotationStep;
  rotateCarousel(steps * rotationStep);
};

// 自动播放功能
const startAutoPlay = () => {
  if (autoPlayInterval) {
    clearInterval(autoPlayInterval);
  }

  autoPlayInterval = setInterval(() => {
    rotateCarousel(-rotationStep);
  }, 5000) as unknown as number;
};

// // 初始化开启播放
// onMounted(() => {
//   startAutoPlay();
// });

//通过computed计算,当前卡片的样式
const getClassName = computed(() => {
  // 计算当前激活的元素索引
  const currentIndex = (currentRotation.value / 60) % totalItems;
  // 使用 Math.floor 来确保索引是整数 Math.abs保证是正数
  const activeIndex = Math.abs(Math.floor(currentIndex));
  console.log("🚀 ~ activeIndex:", activeIndex);

  return activeIndex;
});

onUnmounted(() => {
  if (autoPlayInterval) {
    clearInterval(autoPlayInterval);
  }
});
</script>

<style lang="scss" scoped>
.container {
  background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
  min-height: 100vh;
  color: #f8fafc;
  padding: 20rpx;
  box-sizing: border-box;
}

.gallery-container {
  position: relative;
  width: 100%;
  height: 500px;
  display: flex;
  justify-content: center;
  align-items: center;
  perspective: 1200px; /* 增加透视距离,增强3D效果 */
}

.carousel {
  position: relative;
  width: 220px; /* 缩小卡片宽度 */
  height: 300px; /* 缩小卡片高度 */
  transform-style: preserve-3d;
  transition: transform 0.8s ease-out; /* 减慢过渡速度,使旋转更平滑 */
}

.carousel-item {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
  transition: transform 0.5s ease, box-shadow 0.5s ease;
  opacity: 0.9; /* 稍微降低不透明度,突出当前卡片 */
}

/* 选中卡片的样式强化 */
.carousel-item-active {
  opacity: 1;
  box-shadow: 0 0 30px rgba(22, 93, 255, 0.7);
}

.carousel-image {
  width: 100%;
  height: 100%;
  transition: transform 0.5s ease;
}

.nav-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(22, 93, 255, 0.8);
  color: white;
  border: none;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
  transition: all 0.3s ease;
}

.nav-btn:active {
  background: rgba(22, 93, 255, 1);
  transform: translateY(-50%) scale(1.1);
  box-shadow: 0 0 15px rgba(22, 93, 255, 0.7);
}

.nav-btn.prev {
  left: 5%;
}

.nav-btn.next {
  right: 5%;
}

.gallery-indicators {
  display: flex;
  justify-content: center;
  margin-top: 30px;
  gap: 10px;
}

.indicator {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: rgba(248, 250, 252, 0.3);
  transition: all 0.3s ease;
}

.indicator.active {
  background: rgba(22, 93, 255, 1);
  transform: scale(1.2);
  box-shadow: 0 0 10px rgba(22, 93, 255, 0.7);
}

.gallery-info {
  max-width: 800px;
  margin: 30px auto;
  text-align: center;
  padding: 0 20px;
}

.gallery-title {
  font-size: 36rpx;
  font-weight: 700;
  margin-bottom: 15px;
  color: #f8fafc;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}

.gallery-desc {
  font-size: 28rpx;
  color: rgba(248, 250, 252, 0.8);
  line-height: 1.6;
}
</style>

实际应用场景

这种3D圆柱形旋转画廊适用于:

  • 产品展示页面

  • 相册或图片集应用

  • 酒店或旅游应用的房间/景点展示

  • 电商应用的商品展示

效果展示

相关推荐
lilian23316 小时前
HarmonyOS 7 新特性(十三)|3DGS 模型加载、交互与性能回退
3d·交互·harmonyos
DolitD20 小时前
云流技术深度剖析:单服务器下如何实现3D应用的多实例并发?
java·服务器·前端·3d·云原生·云计算
Liudef0620 小时前
腾讯混元Hunyuan3D-Part:重新定义3D部件生成的革命性架构
人工智能·3d·架构·腾讯混元hunyuan3d
RisunJan21 小时前
uni-app 面试知识点梳理
面试·职场和发展·uni-app
郑州光合科技余经理1 天前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php
2601_967659892 天前
大学实训室网络仿真环境搭建:Ranplan 3D射线跟踪双版本技术解析
网络·3d
七77.2 天前
SceneAssistant: A Visual Feedback Agent for Open-Vocabulary 3D Scene Generation
3d·agent·世界模型
rockingdingo2 天前
Codex Claude 智能体做3D/潮玩/IP设计——装上Craftsman Agent工匠智能体Skills 游泳男孩IP案例分享
网络协议·tcp/ip·3d
3D可视化大侠3 天前
数字孪生时空数据可视化:CIMPro 孪大师中的轨迹与粒子实现
3d·信息可视化
LONGZETECH3 天前
新能源汽车动力电池实训教学痛点与虚拟仿真技术解决方案
c语言·3d·unity·架构·汽车·汽车教学软件