当前位置: 首页 > article >正文

Vue 3:基于按钮切换动态图片展示(附Demo)

目录

  • 前言
  • 1. Demo
  • 2. 升级Demo
  • 3. 终极Demo

前言

原先写过类似的知识点:

  1. 详细分析el-breadcrumb 面包屑的基本知识(附Demo)
  2. 详细分析el-card中的基本知识(附Demo)

本篇博客将介绍如何通过点击按钮切换不同的图片,并优化交互体验,使页面更流畅、响应更快

主要的知识点:

  • Vue 3 组件化开发 - 通过 setup 组合式 API 进行数据管理
  • 动态绑定 class - 根据当前选中的图片高亮按钮
  • 过渡动画 transition - 让图片切换更流畅
  • 图片预加载 - 避免切换时的卡顿
  • 样式优化 - 让按钮和图片展示更美观

1. Demo

最初始的版本Demo:

<template>
  <div>
    <!-- 按钮组 -->
    <div class="button-group">
      <button v-for="image in images" :key="image.id" @click="currentImage = image.src">
        {{ image.label }}
      </button>
    </div>

    <!-- 动态展示图片 -->
    <div class="image-container">
      <img v-if="currentImage" :src="currentImage" alt="Dynamic Image" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 当前选中的图片
const currentImage = ref(null)

// 定义按钮与对应的图片路径(public 目录下的图片)
const images = [
  { id: 'img1', label: '图片1', src: '/image1.png' },
  { id: 'img2', label: '图片2', src: '/image2.png' },
  { id: 'img3', label: '图片3', src: '/image3.png' },
  { id: 'img4', label: '图片4', src: '/image4.png' }
]
</script>

<style scoped>
.button-group {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}
button {
  padding: 8px 12px;
  cursor: pointer;
}
.image-container {
  text-align: center;
}
img {
  max-width: 100%;
  height: auto;
}
</style>

截图如下:

在这里插入图片描述

2. 升级Demo

这一版本有个缺点,就是卡顿,但是他的样式很好看!

  • 图片加载问题:每次点击都会重新加载图片,导致延迟
  • 样式导致的渲染卡顿:box-shadow、border 变化可能导致重绘
  • 事件未优化:Vue 响应式 ref 更新时,可能导致不必要的 DOM 计算

更改相关样式以及按钮:

<template>
  <div class="container">
    <!-- 按钮组 -->
    <div class="button-group">
      <button v-for="image in images" :key="image.id" @click="currentImage = image.src" class="image-button">
        <img :src="image.src" alt="Preview" class="button-thumbnail" />
        <span>{{ image.label }}</span>
      </button>
    </div>

    <!-- 动态展示图片 -->
    <div class="image-container">
      <img v-if="currentImage" :src="currentImage" alt="Dynamic Image" class="main-image" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 当前选中的图片
const currentImage = ref(null)

// 定义按钮与对应的图片路径(public 目录下的图片)
const images = [
  { id: 'img1', label: '图片1', src: '/favicon.ico' },
  { id: 'img2', label: '图片2', src: '/image2.png' },
  { id: 'img3', label: '图片3', src: '/image3.png' },
  { id: 'img4', label: '图片4', src: '/image4.png' },
  { id: 'img5', label: '图片5', src: '/deepSeaField.png' },
  { id: 'img6', label: '图片6', src: '/image2.png' },
  { id: 'img7', label: '图片7', src: '/image3.png' },
  { id: 'img8', label: '图片8', src: '/image4.png' }
]
</script>

<style scoped>
/* 整体容器 */
.container {
  max-width: 800px;
  margin: 0 auto;
  text-align: center;
}

/* 按钮组:使用网格布局 */
.button-group {
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* 4 列布局 */
  gap: 15px;
  margin-bottom: 20px;
}

/* 按钮样式 */
.image-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: white;
  border: 2px solid #ddd;
  border-radius: 12px;
  padding: 10px;
  cursor: pointer;
  transition: all 0.3s ease-in-out;
  box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
}

.image-button:hover {
  background: #f0f0f0;
  transform: scale(1.05);
}

/* 按钮里的小缩略图 */
.button-thumbnail {
  width: 50px;
  height: 50px;
  border-radius: 8px;
  object-fit: cover;
  margin-bottom: 5px;
}

/* 主要图片 */
.image-container {
  margin-top: 20px;
}

.main-image {
  max-width: 100%;
  height: auto;
  border-radius: 10px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
</style>

截图如下:

在这里插入图片描述

后续继续整改优化:

<template>
  <div class="container">
    <!-- 按钮组 -->
    <div class="button-group">
      <button v-for="image in images" :key="image.id" @click="currentImage = image.src" class="image-button">
        <img :src="image.src" alt="Preview" class="button-thumbnail" />
        <span>{{ image.label }}</span>
      </button>
    </div>

    <!-- 动态展示图片 -->
    <div class="image-container">
      <img v-if="currentImage" :src="currentImage" alt="Dynamic Image" class="main-image" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 当前选中的图片
const currentImage = ref(null)

// 定义按钮与对应的图片路径(public 目录下的图片)
const images = [
  { id: 'img1', label: '图片1', src: '/favicon.ico' },
  { id: 'img2', label: '图片2', src: '/image2.png' },
  { id: 'img3', label: '图片3', src: '/image3.png' },
  { id: 'img4', label: '图片4', src: '/image4.png' },
  { id: 'img5', label: '图片5', src: '/deepSeaField.png' },
  { id: 'img6', label: '图片6', src: '/image2.png' },
  { id: 'img7', label: '图片7', src: '/image3.png' },
  { id: 'img8', label: '图片8', src: '/image4.png' }
]
</script>

<style scoped>
/* 整体容器 */
.container {
  max-width: 1000px;
  margin: 0 auto;
  text-align: center;
}

/* 按钮组:一行排列 */
.button-group {
  display: flex;
  justify-content: space-around;
  gap: 10px;
  margin-bottom: 20px;
  flex-wrap: wrap; /* 保证小屏设备时自动换行 */
}

/* 按钮样式 */
.image-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: white;
  border: 2px solid #ddd;
  border-radius: 12px;
  padding: 10px;
  cursor: pointer;
  transition: all 0.3s ease-in-out;
  box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
  width: 100px; /* 固定宽度 */
  height: 120px; /* 固定高度 */
}

.image-button:hover {
  background: #f0f0f0;
  transform: scale(1.05);
}

/* 按钮里的小缩略图 */
.button-thumbnail {
  width: 50px;
  height: 50px;
  border-radius: 8px;
  object-fit: cover;
  margin-bottom: 5px;
}

/* 主要图片 */
.image-container {
  margin-top: 20px;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 600px; /* 固定高度,居中展示大图 */
  border: 2px solid #ddd;
  border-radius: 10px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.main-image {
  max-width: 100%;
  max-height: 100%; /* 确保图片不会超出容器 */

  object-fit: contain; /* 保持图片比例 */
}
</style>

截图如下:

在这里插入图片描述

3. 终极Demo

后续发现会很卡顿

优化点

  • 去除黑色边框:
    由于 <button> 在不同浏览器默认会有 outline 选中效果,添加 outline: none 解决
    通过 border: none 避免额外边框影响

  • 流畅过渡动画:
    按钮缩略图 采用 background-image,提高图片加载速度
    主图片切换 采用 Vue Transition 并优化 opacity 过渡,使切换更顺畅

  • 减少卡顿:
    预加载所有图片,防止首次切换时加载延迟
    优化 changeImage 逻辑,避免重复更新 currentImage

<template>
  <div class="container">
    <!-- 按钮组 -->
    <div class="button-group">
      <button
        v-for="image in images"
        :key="image.id"
        @click="changeImage(image.src)"
        class="image-button"
        :class="{ active: currentImage === image.src }"
      >
        <div class="button-thumbnail" :style="{ backgroundImage: `url(${image.src})` }"></div>
        <span>{{ image.label }}</span>
      </button>
    </div>

    <!-- 动态展示图片 -->
    <div class="image-container">
      <transition name="fade" mode="out-in">
        <img v-if="currentImage" :src="currentImage" alt="Dynamic Image" class="main-image" />
      </transition>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

// 定义按钮与对应的图片路径
const images = [
  { id: 'img1', label: '未来制造', src: '/futureManufacturing.png' },
  { id: 'img2', label: '未来材料', src: '/futureMaterials.png' },
  { id: 'img3', label: '未来信息', src: '/futureInformation.png' },
  { id: 'img4', label: '未来能源', src: '/futureEnergy.png' },
  { id: 'img5', label: '未来医疗', src: '/futureMedical.png' },
  { id: 'img6', label: '人工智能', src: '/artificialIntelligence.png' },
  { id: 'img7', label: '数字经济', src: '/digitalEconomy.png' },
  { id: 'img8', label: '低空经济', src: '/lowAltitudeEconomy.png' },
  { id: 'img9', label: '深海领域', src: '/deepSeaField.png' }
]

// 默认选中第一张图片
const currentImage = ref(images[0].src)

// 切换图片
const changeImage = (src) => {
  if (currentImage.value !== src) {
    currentImage.value = src
  }
}

// 预加载图片,减少切换时的卡顿
const preloadImages = () => {
  images.forEach(image => {
    const img = new Image()
    img.src = image.src
  })
}

onMounted(preloadImages)
</script>

<style scoped>
/* 整体容器 */
.container {
  max-width: 1200px;
  margin: 0 auto;
  text-align: center;
}

/* 按钮组 */
.button-group {
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-wrap: nowrap;
  overflow-x: auto;
  padding: 10px 0;
  scrollbar-width: none; /* 隐藏滚动条 */
}

/* 按钮样式 */
.image-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: white;
  border: none;
  border-radius: 10px;
  padding: 5px;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease;
  width: 90px;
  height: 110px;
  outline: none; /* 去除点击时的黑框 */
}

.image-button:hover {
  transform: scale(1.05);
}

/* 选中状态 */
.image-button.active {
  background: #e3f2fd;
  box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}

/* 按钮里的小缩略图 */
.button-thumbnail {
  width: 50px;
  height: 50px;
  border-radius: 8px;
  background-size: cover;
  background-position: center;
  margin-bottom: 5px;
}

/* 主要图片 */
.image-container {
  will-change: transform, opacity;
  margin-top: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 600px;
  border-radius: 10px;
  width: 100%; /* 增加宽度 */
  max-width: 1200px; /* 限制最大宽度,防止过大 */
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  overflow: hidden;
}

.main-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* 过渡动画 */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s ease-in-out;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

http://www.kler.cn/a/553912.html

相关文章:

  • 【Web前端开发精品课 HTML CSS JavaScript基础教程】第二十四章课后题答案
  • Centos开机自启动
  • 电路元器件知识:稳压二极管
  • Elasticsearch 混合搜索 - Hybrid Search
  • 【Java项目】基于SpringBoot的【校园台球厅人员与设备管理系统】
  • 【蓝桥杯集训·每日一题2025】 AcWing 6118. 蛋糕游戏 python
  • VMware17Pro虚拟机安装macOS教程(超详细)
  • 我的电脑是 3070ti 能用那个级别的deepseek
  • Redis的基础使用
  • Scrapy:DownloaderAwarePriorityQueue队列设计详解
  • 【系统架构设计师】虚拟机体系结构风格
  • 【从0做项目】Java搜索引擎(6) 正则表达式鲨疯了优化正文解析
  • 【项目日记】仿RabbitMQ实现消息队列 --- 模块设计
  • 关于视频抽帧调用虹软人脸识别的BufferedImage读取优化策略
  • 基于微信小程序的民宿短租系统设计与实现(ssm论文源码调试讲解)
  • 如何在Ubuntu服务器上快速安装GNOME桌面环境
  • ​44页PDF | 天津大学深度解读DeepSeek:原理与效应(附下载)
  • 解决DeepSeek服务器繁忙问题的实用指南
  • UE5.3 C++ 通过Spline样条实现三维连线,自己UV贴图。
  • Linux-----进程(多任务)