WEB :实战演练——从零实现一个交互轮播图(附源码)

文章目录

  • 一、轮播图整体功能规划
  • 二、HTML结构深度解析
  • 三、CSS样式实现细节
    • 1. 定位系统详解
    • 2. 显示/隐藏机制
    • 3. 按钮交互效果实现
    • 4. 纯CSS箭头实现
    • 5. 指示器:当前位置可视化
  • 四、JavaScript逻辑深入解析
    • 1. 核心变量与DOM获取
    • 2. 图片切换函数(核心逻辑)
    • 3. 前后切换函数(边界处理)
    • 4. 自动播放控制
    • 5. 指示器完整交互
    • 6. 事件绑定与初始化

轮播图作为前端开发中的经典组件,广泛应用于网站首页、产品展示等场景。它不仅能在有限空间内展示多张图片,还能通过动态效果提升用户体验。本文将从结构设计、样式实现到交互逻辑,详细讲解如何从零构建一个功能完善的轮播图。

轮播图实现效果:

轮播图

一、轮播图整体功能规划

在开始编码前,我们需要明确轮播图的核心功能:

  • 自动播放:图片按固定时间间隔自动切换
  • 手动切换:通过左右按钮控制图片切换
  • 指示器导航:底部小圆点显示当前位置,点击可快速跳转到对应图片
  • 交互反馈:鼠标悬停时暂停自动播放,显示操作按钮;离开时恢复自动播放
  • 平滑过渡:图片切换时使用动画效果,避免生硬跳转

二、HTML结构深度解析

轮播图的HTML结构看似简单,实则蕴含了清晰的层次设计:

<div class="box"><!-- 图片容器组 --><div class="box-img"><img src="img/albumFolklore.jpg"> </div><div class="box-img"><img src="img/albumST.jpg"> </div><div class="box-img"><img src="img/albumSpring.jpg" > </div><div class="box-img"><img src="img/albumNTM.jpg"> </div> <!-- 控制按钮 --><div class="left"> </div><div class="right"> </div><!-- 指示器 --><div class="dot"><ul id="dot-list"><li class="active" data-index="0"></li><li data-index="1"></li><li data-index="2"></li><li data-index="3"></li></ul></div>
</div>

结构设计考量

  • 为什么使用.box-img包裹图片而非直接操作img标签?

    • 便于统一控制图片容器的显示状态(opacity)
    • 为后续可能的图片加载动画预留空间
    • 可以在不修改图片本身的情况下添加过渡效果
  • 指示器为什么使用data-index属性?

    • 建立指示器与图片的一一对应关系
    • 无需通过复杂计算获取索引,直接从DOM中读取
    • 提高代码可读性和可维护性

三、CSS样式实现细节

1. 定位系统详解

.box{position: relative;
}
.box-img img{position: absolute;top: 0;left: 0;
}

这是轮播图实现的核心基础,通过定位系统实现了"多图叠加"效果:

  • .box设置position: relative后,成为了所有子元素的"定位上下文"
  • 所有图片设置position: absolutetop: 0; left: 0,使它们都从容器左上角开始定位
  • 最终效果是所有图片在视觉上重叠在一起,为后续的显示/隐藏切换奠定基础

2. 显示/隐藏机制

.box-img{opacity: 0;transition: opacity 0.5s ease-in-out;
}
.box-img:nth-child(1){opacity: 1;
}

为什么选择opacity而不是其他方案?

  • 方案对比:

    • display: none:完全移除元素,无法实现过渡动画
    • visibility: hidden:元素仍占据空间,且过渡效果有限
    • opacity: 0:元素仍存在于页面中(可响应事件),支持平滑过渡
  • transition属性详解:

    • ease-in-out:缓动函数(开始和结束时较慢,中间较快)

3. 按钮交互效果实现

按钮设置了三种状态:

  1. 鼠标移出盒子时隐藏
  2. 鼠标移入盒子但未移入按钮为浅灰色
  3. 鼠标移入按钮为深灰色

按钮演示

.left,.right{position: absolute;top: 225px; /* 垂直居中 */transform: translateY(-50%); /* 精确居中 */width: 35px;height: 35px;display: flex; /* 确保后面::before伪元素选择器起作用 *//*使箭头位于圆中心*/align-items: center;justify-content: center;border-radius: 50%; /* 圆形按钮 */z-index: 10; /* 确保在图片上方 */cursor: pointer; /* 鼠标悬停显示手型 */opacity: 0; /*隐藏*/background-color: rgba(0,0,0,0.2);/* 浅灰 */color: white;/*箭头颜色*/transition: all 0.3s ease; /* 按钮自身的动画效果 */
}/* 鼠标悬停盒子时显示按钮 */
.box:hover .left, .box:hover .right{opacity:1; /*显示按钮*/
}.left{left: 10px;
}
.right{right: 10px;
}

这是一个典型的"条件显示"交互模式:

  • 默认状态下按钮隐藏(opacity:0
  • 当鼠标悬停在容器上时(.box:hover),通过后代选择器激活按钮显示状态
  • transform: translateY(-50%)确保按钮在垂直方向上精确居中

4. 纯CSS箭头实现

.left::before, .right::before{content: '';width: 12px;height: 12px;border-top: 2px solid white;border-left: 2px solid white;
}
.left::before{transform: translateX(2px) rotate(-45deg);
}
.right::before{transform: translateX(-2px) rotate(135deg);
}

这是一种无需图片的箭头实现方案:

  • 使用::before伪元素创建一个正方形元素
  • 通过border-topborder-left绘制两条边(模拟箭头的两条边)
  • 利用rotate旋转实现箭头方向:
    • 左箭头:旋转-45度
    • 右箭头:旋转135度(相当于-225度)
  • 微调translateX使箭头视觉上居中

优点: 减少HTTP请求、易于修改颜色和大小、缩放不失真

5. 指示器:当前位置可视化

指示器演示

.dot{position: absolute;bottom: 15px;right: 70px; /* 定位在右下角 */
}.dot ul li{width: 10px;height: 10px;border-radius: 100%; /* 圆形指示器 */background-color: #737171;float: left;margin-right: 15px;cursor: pointer;transition: all 0.3s ease; /* 状态变化动画 */
}/* 指示器交互效果 */
.dot ul li.active{background-color: #ffffff;transform: scale(1.4); /* 当前项更大 */box-shadow: 0 0 8px rgba(255,255,255,0.8); /* 高亮效果 */
}

指示器作用:

  • 直观显示当前是第几张图片及总数量
  • 点击可快速跳转到对应图片
  • 通过active类区分当前选中状态

四、JavaScript逻辑深入解析

1. 核心变量与DOM获取

// 获取 DOM元素
const imgs = document.querySelectorAll('.box-img');
const prevB = document.querySelector(".left");
const nextB = document.querySelector('.right');
const dots = document.getElementById('dot-list').querySelectorAll('li');// 状态变量
let currentIndex = 0;
const imgCnt = imgs.length;
let autoTimer = null;

变量作用详解:

  • imgs:获取所有图片容器的集合(NodeList),便于批量操作
  • currentIndex:当前显示图片的索引,是整个轮播逻辑的"状态核心"
  • imgCnt:存储图片总数,避免重复计算imgs.length
  • autoTimer:存储计时器ID,用于控制自动播放的开启与关闭

2. 图片切换函数(核心逻辑)

function switchToImg(index) {// 隐藏所有图片imgs.forEach( img => {img.style.opacity = 0;});// 显示目标图片imgs[index].style.opacity=1;// 更新当前索引currentIndex = index;// 更新指示器状态dots.forEach(dot => {dot.classList.remove('active');});dots[index].classList.add('active')
}

这个函数是轮播图的"心脏",负责完成一次完整的图片切换:

执行步骤分解:

  1. 遍历所有图片容器,将它们的透明度设为0(隐藏)
  2. 将目标索引对应的图片容器透明度设为1(显示)
    • 此时会触发CSS中定义的transition动画,实现淡入效果
  3. 更新currentIndex为当前索引,保持状态同步
  4. 更新指示器状态:
    • 先移除所有指示器的active
    • 再给当前索引对应的指示器添加active
    • 这会触发指示器的CSS状态变化(颜色、大小等)

3. 前后切换函数(边界处理)

// 向左切换
function prevImg(){currentIndex = (currentIndex - 1 + imgCnt) % imgCnt;switchToImg(currentIndex);
}// 向右切换
function nextImg(){currentIndex = (currentIndex + 1) % imgCnt;switchToImg(currentIndex);
}

这两个函数解决了轮播图的"循环切换"问题,关键在于边界处理:

  • 向右切换逻辑:

    • 正常情况:索引+1(如从0→1,1→2)
    • 边界情况:当索引是最后一张(3)时,+1后应该变为0
    • 实现:(currentIndex + 1) % imgCnt,利用取模运算自动回绕
  • 向左切换逻辑:

    • 正常情况:索引-1(如从2→1,1→0)
    • 边界情况:当索引是0时,-1后应该变为最后一张(3)
    • 实现:(currentIndex - 1 + imgCnt) % imgCnt
    • imgCnt是为了避免出现负数(如0-1=-1,+4=3,再取模仍为3)

4. 自动播放控制

// 开始自动播放
function startAutoPlay(){stopAutoPlay();autoTimer = setInterval(nextImg, 3000);
}// 停止自动播放
function stopAutoPlay(){clearInterval(autoTimer);
}

自动播放功能的实现关键点:

  • 为什么在startAutoPlay中先调用stopAutoPlay

    • 防止多次调用startAutoPlay导致创建多个计时器
    • 确保每次开始自动播放前都清除了之前的计时器
    • 避免轮播速度越来越快的问题
  • 时间间隔选择:3000ms(3秒)是一个平衡用户浏览和交互的常用值

    • 太短:用户来不及看清内容
    • 太长:轮播效果不明显

5. 指示器完整交互

function initDots(){dots.forEach((dot,index) => {// 点击事件dot.addEventListener('click',() => {switchToImg(index);startAutoPlay();});// 鼠标移入事件dot.addEventListener('mouseenter',() => {switchToImg(index);stopAutoPlay();});// 鼠标离开事件dot.addEventListener('mouseleave', startAutoPlay);// 设置数据索引dot.setAttribute('data-index',index);});dots[0].classList.add('active');
}

指示器实现了三种交互方式,提升用户体验:

  • 点击交互:

    • 直接跳转到对应图片(调用switchToImg(index)
    • 跳转后重新开始自动播放计时(startAutoPlay()
  • 悬停交互:

    • 鼠标移入时跳转到对应图片并暂停自动播放
    • 鼠标离开时恢复自动播放
    • 这种设计允许用户仔细查看某张图片,提升浏览体验
  • 初始化:确保页面加载时第一个指示器处于激活状态

6. 事件绑定与初始化

// 按钮点击事件
prevB.addEventListener('click',() => {prevImg();startAutoPlay();
});
nextB.addEventListener('click',() => {nextImg();startAutoPlay();
});// 容器悬停事件
const box = document.querySelector('.box');
box.addEventListener('mouseenter', stopAutoPlay);
box.addEventListener('mouseleave', startAutoPlay);// 初始化执行
initDots();
startAutoPlay();

事件绑定将所有功能串联起来,形成完整的交互闭环:

  • 按钮点击:

    • 点击后切换图片
    • 同时重启自动播放计时器(避免手动操作后立即自动切换)
  • 容器悬停:

    • 鼠标进入容器时暂停自动播放(方便用户查看当前图片)
    • 鼠标离开容器时恢复自动播放
    • 这个设计优先考虑了用户主动浏览的需求
  • 初始化流程:

    1. 先初始化指示器(initDots()
    2. 再启动自动播放(startAutoPlay()
    3. 确保页面加载完成后轮播图即可正常工作

声明:源码是本人的部分期末作业,以初学者的角度思考问题,代码相对实际开发还欠缺优化,仅仅为初学者提供思路,欢迎大佬提出优化意见。

源码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.box{width: 1000px;height: 500px;position: relative;margin: 15px auto;}.box-img img{width: 1000px;height: 500px;position: absolute;top: 0;left: 0;}.box-img{opacity: 0;transition: opacity 0.5s ease-in-out;}.box-img:nth-child(1){opacity: 1;}.left,.right{opacity: 0;position: absolute;transform: translateY(-50%);top: 225px;width: 35px;height: 35px;align-items: center;justify-content: center;border-radius: 50%;z-index: 10;cursor: pointer;background-color: rgba(0,0,0,0.2);color: white;font-size: 24px;transition: all 0.3s ease; /* 按钮自身的动画效果 */display: flex;}.box:hover .left, .box:hover .right{opacity: 1;}.left{left: 10px;}.right{right: 10px;}.left::before, .right::before{content: '';width: 12px;height: 12px;border-top: 2px solid white;border-left: 2px solid white;}.left::before{transform: translateX(2px) rotate(-45deg);}.right::before{transform: translateX(-2px) rotate(135deg);}.left:hover, .right:hover{background-color: rgba(0,0,0,0.7);transform: translateY(-50%) scale(1.1);opacity: 1; box-shadow: 0 0 15px rgba(255,255,255,0.3);}.dot{position: absolute;bottom: 15px;right: 70px;}.dot ul{padding: 0;margin: 0;list-style: none;}.dot ul li{width: 10px;height: 10px;border-radius: 100%;background-color: #737171;float: left;margin-right: 15px;cursor: pointer;transition: all 0.3s ease;}.dot ul li.active{background-color: #ffffff;transform: scale(1.4);box-shadow: 0 0 8px rgba(255,255,255,0.8);}</style>
</head>
<body>
<div class="box"><!-- 轮播图片容器 --><div class="box-img"><img src="img/albumFolklore.jpg"> </div><div class="box-img"><img src="img/albumST.jpg"> </div><div class="box-img"><img src="img/albumSpring.jpg" > </div><div class="box-img"><img src="img/albumNTM.jpg"> </div> <!-- 左右切换按钮 --><div class="left"> </div><div class="right"> </div><!-- 指示器(小圆点) --><div class="dot"><ul id="dot-list"><li class="active" data-index="0"></li><li data-index="1"></li><li data-index="2"></li><li data-index="3"></li></ul></div></div>
<script>
const imgs = document.querySelectorAll('.box-img');
const prevB = document.querySelector(".left");
const nextB = document.querySelector('.right');
const dots = document.getElementById('dot-list').querySelectorAll('li');let currentIndex = 0;
const imgCnt = imgs.length;//初始化
function initDots(){dots.forEach((dot,index) => {//点击dot.addEventListener('click',() => {switchToImg(index);startAutoPlay();});//移入dot.addEventListener('mouseenter',() => {switchToImg(index);stopAutoPlay();})//离开dot.addEventListener('mouseleave',startAutoPlay);dot.setAttribute('data-index',index)});dots[0].classList.add('active');
}//切换
function switchToImg(index) {imgs.forEach( img => {img.style.opacity = 0;});imgs[index].style.opacity=1;currentIndex = index;dots.forEach(dot => {dot.classList.remove('active');});dots[index].classList.add('active')
}//向左切换
function prevImg(){currentIndex = (currentIndex - 1 + imgCnt) % imgCnt;switchToImg(currentIndex);
}//向右切换
function nextImg(){currentIndex = (currentIndex + 1) % imgCnt;switchToImg(currentIndex);
}//计时器
let autoTimer = null;
function startAutoPlay(){stopAutoPlay();autoTimer = setInterval(nextImg,3000);
}function stopAutoPlay(){clearInterval(autoTimer);
}//事件绑定
prevB.addEventListener('click',() => {prevImg();startAutoPlay();
});
nextB.addEventListener('click',() => {nextImg();startAutoPlay();
});initDots();
startAutoPlay();const box = document.querySelector('.box');
box.addEventListener('mouseenter',stopAutoPlay);
box.addEventListener('mouseleave',startAutoPlay);
</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/90165.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/90165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MCP 协议详细分析一 initialize ping tools/list tools/call

MCP 协议详细分析一 &#xff08;initialize ping tools/list tools/call) 本节基于 实现一个 java 的mcp client 调用的 一个python 的mcp server 的日志&#xff0c;完整展示一次典型的 MCP Java SDK 通信流程、工具调用、通知机制与日志记录&#xff0c;仅包含 echo-simple…

SLAM学习资料记录

ORB_SLAM2 创建自己的数据集&#xff08;还未使用&#xff09; 【SLAM实战篇】Ubuntu 20.04版本&#xff08;OpenCV版本4.5.3&#xff09;对于ORB-SLAM2安装运行&#xff0c;代码编译&#xff0c;自己的数据集构造_ubuntu20.04 安装运行orb_slam2算法-CSDN博客 卡尔曼滤波数据…

用Phi-3 Mini微调实现英文到尤达语翻译

用Phi-3 Mini微调实现英文到尤达语翻译 引言 本文将带你快速上手大模型微调实践——以微软的Phi-3 Mini 4K Instruct模型为例&#xff0c;教你如何将其微调为一个能把英文翻译成"尤达语"&#xff08;《星球大战》中尤达大师的独特说话风格&#xff09;的模型。这是一…

AI助力,轻松实现人声分离伴奏提取

亲爱的小伙伴们&#xff01;前段时间&#xff0c;有一位同事家的可爱小孩参加了一场英语演讲比赛。同事找到我&#xff0c;希望我能帮个忙&#xff0c;把讲视频中的人声去掉&#xff0c;只提取出其中相应的伴奏。今天&#xff0c;我就来和大家分享一下究竟如何实现从 MP4 视频中…

第1章第2章笔记

OSI参考模型---开放式系统互联模型---OSI/RM ISO--->国际标准化组织&#xff1b;特点&#xff1a;先有模型&#xff0c;在又协议。 OSI七层参考模型&#xff1a;应用层 --- 提供网络服务&#xff1b;自然语言-->编码表示层 --- 对数据的处理&#xff1b;格式化&#xff0…

图的BFS和DFS

一&#xff0c;图的遍历逻辑1.之前我们学了图的存储&#xff0c;可以邻接表存和邻接矩阵存。现在我们要学习图的遍历操作和树类似可以分为深度遍历和广度遍历&#xff0c;而深度遍历也是用递归实现&#xff0c;广度遍历是用队列实现2.深度遍历(DFS)a.确定起点b.找到一条边按顺时…

WWDC 25 给自定义 SwiftUI 视图穿上“玻璃外衣”:最新 Liquid Glass 皮肤详解

引子 各位 iOS 足球体育健儿们&#xff0c;且听我一言&#xff01;想当年在《少林足球》里&#xff0c;阿星一句“做人如果没梦想&#xff0c;那跟咸鱼有什么分别啊&#xff1f;”点燃了多少人的江湖梦。 如今在 SwiftUI 江湖里&#xff0c;Apple 于 WWDC 25 推出的 Liquid Gl…

Day01_C++

01.思维导图02.方法一&#xff1a;#include <iostream> #include <cstring> #include <iostream> using namespace std; class mystring { private:char* buf;int len;public:mystring(const char* str);void copy(const char* ptr);void copy(mystring ptr)…

C语言学习(days09)

二维数组的定义与特性二维数组的声明格式为&#xff1a;类型说明符 数组名[表达式1][表达式2];[下标1]表示行索引&#xff0c;[下标2]表示列索引。二维数组可视为由多个一维数组组成&#xff0c;a[0]表示第0行的首地址&#xff08;即一维数组地址&#xff09;a[0][0]表示第0的第…

WIFI路由器长期不重启,手机连接时提示无IP分配

今天在公司&#xff0c;突然发现手机连不上公司WIFI。每次链接&#xff0c;提示无IP分析。我以为是我手机出问题了&#xff0c;想复位一下。后来一想万一复位还是不灵&#xff0c;怎么办&#xff1f;同事认为是路由器没有重启的原因。于是找到路由器&#xff0c;重启&#xff0…

【前沿技术动态】【AI总结】RustFS:从 0 到 1 打造下一代分布式对象存储

目录1 引言&#xff1a;为什么我们又需要一个新的对象存储2 RustFS 全景速览3 技术架构深度拆解3.1 整体拓扑3.2 关键数据结构&#xff08;rust 伪代码&#xff09;3.3 读写路径&#xff08;写放大 < 1.1&#xff09;4 核心源码导读4.1 关键函数跟踪4.2 一段最小可复现示例5…

ImageNet1K数据集的下载解压与处理

前言 博主因为这个数据集踩了好多坑&#xff0c;浪费了好几天时间&#xff0c;最近终于找到了高效的办法&#xff0c;写此篇文章来记录具体操作方法&#xff0c;也希望可以帮助到有需要的人。&#xff08;主要是在云服务器是使用&#xff09; 下载数据集 一共下载三个文件&…

OkHttp 与 Room 结合使用:构建高效的 Android 本地缓存策略

前言在现代 Android 应用开发中&#xff0c;网络请求与本地数据持久化是两大核心功能。OkHttp 作为强大的网络请求库&#xff0c;与 Jetpack Room 持久化库的结合使用&#xff0c;可以创建高效的数据缓存策略&#xff0c;提升应用性能和用户体验。本文将详细介绍如何将这两者完…

Nacos中feign.FeignException$BadGateway: [502 Bad Gateway]

Nacos中feign.FeignException$BadGateway: [502 Bad Gateway] 文章目录Nacos中feign.FeignException$BadGateway: [502 Bad Gateway]背景原因背景 Mac本地运行Nacos微服务项目&#xff0c;调用服务失败 原因 关闭本地代理clash或者其他&#xff0c;windows没发现问题&#x…

基于deepseek的LORA微调

LORA微调&#xff1a; 核心是&#xff1a;低秩转换&#xff0c;减少参数。冻结大部分&#xff0c;调节部分模块(注意力模块的Wq&#xff0c;Wk&#xff0c;Wv)。 调整过后得到一个lora.safetensors, 内部记录了(detail W: 即部分修改的W)。推理使用原权重和lora权重。 具体操…

Linux运维新手的修炼手扎之第22天

Tomcat服务1 java项目部署方式&#xff1a;war包部署、jar包部署、源代码部署2 Ubuntu环境部署Java - openjdk[熟练]:#安装软件rootubuntu24-13:~# apt update; apt list openjdk*rootubuntu24-13:~# apt install openjdk-11-jdk -y#检测效果rootubuntu24-13:~# whereis javaja…

Python爬虫实战:研究Genius库相关技术

1. 引言 在当今数字化时代,音乐数据的分析与挖掘成为了音乐学、计算机科学等领域的研究热点。歌词作为音乐的重要组成部分,蕴含着丰富的情感、文化和社会信息。通过对歌词数据的分析,可以揭示音乐风格的演变、流行趋势的变化以及社会情绪的波动等。 Genius 是一个专注于歌词…

内核协议栈源码阅读(一) ---驱动与内核交互

文章目录 一、硬中断 1.1 `e100_intr` 1.2 `__netif_rx_schedule` 1.3 补充: 二、软中断 2.1 net_rx_action 2.2 e100_poll 2.3 补充 三、非 NAPI 的软中断处理 3.1 netif_rx 3.2 backlog_dev->poll 3.3 补充 四、总结 以 e100_intr 为例: 一、硬中断 1.1 e100_intr 网卡…

Vue3 面试题及详细答案120道(61-75 )

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

ubuntulinux快捷键

1.复制文件使用cp命令。cp是复制的简写。语法也很简单。使用&#xff0c;cp后跟要复制的文件以及要将其移动到的目的地cp ~/Downloads/your-file.txt ~/Documents/2.复制文件夹为了复制文件夹及其内容&#xff0c;您将需要告诉cp命令以递归方式复制。使用-r标志就足够简单了。c…