js代码一定要写在body后面!不然会报错,网页要先加载
轮播图,纯js实现
静态组件:
1.用div存需要轮播的图片
2.点击切换的按钮prev、next
3.下面的小圆点,用一个div存三个点,第一个点添加active类默认激活
样式:
外容器:由于图片是排一起的,所以外部div要防溢出;动画过渡transition transform平滑过渡
前后切换按钮:开启绝对定位,在外容器的50%处(注意要transform: translateY(-50%););cursor:pointer;鼠标变成小手
下面三个小点:绝对定位,定在下方;left50%(注意这里也要transform: translateX(-50%);)
注意:这里的按钮和小圆点都需要高于图片图层,不然无法点击
transision
js逻辑:
在 DOM 加载完成后执行的轮播图逻辑,平滑过渡动画 + 自动轮播 + 无缝循环 + 手动控制 + 圆点控制
大体结构如下:
-
获取元素(slides、按钮、圆点)
-
初始化变量(索引、计时器、动画锁)
-
显示指定幻灯片
showSlide(i) -
动画结束处理(transitionend)
-
自动轮播(setInterval)
-
左右按钮事件
-
底部圆点事件
-
页面切后台时同步状态
无缝循环:最后一张为第一张图,实现无缝切换
动画锁:防止用户快速点击导致索引错乱
// 轮播图操作
document.addEventListener('DOMContentLoaded', () => {
// ----------- 获取元素 -----------
const inner = document.querySelector('.inner-carousel');
const slides = document.querySelectorAll('.slide');
const prevBtn = document.querySelector('.prev');
const nextBtn = document.querySelector('.next');
const dots = document.querySelectorAll('.dot');
// ----------- 初始化变量 -----------
const total = slides.length - 1; // 最后一张是克隆图
let index = 0; // 当前显示的 slide 索引
let timer; // 自动轮播定时器
let isAnimating = false; // 动画锁,防止快速点击导致 index 错乱
// ----------- 显示指定 slide -----------
function showSlide(i) {
if (isAnimating) return; // 动画未完成时禁止执行
isAnimating = true;
index = i; //更新index
// 添加平滑过渡动画
inner.style.transition = 'transform 0.6s ease';
inner.style.transform = `translateX(-${index * 100}%)`;
// 更新底部圆点高亮
dots.forEach(dot => dot.classList.remove('active'));
//index % total 用于 处理最后一张克隆图 时,让圆点指示第一张
dots[index % total].classList.add('active');
}
// ----------- 监听动画结束事件,用于无缝循环 -----------
inner.addEventListener('transitionend', () => {
isAnimating = false; // 动画结束解锁
// 如果到克隆图(最后一张),瞬间跳回第一张,无缝循环
if (index === total) {
inner.style.transition = 'none';
index = 0;
inner.style.transform = 'translateX(0%)';
}
});
// ----------- 自动轮播 -----------
timer = setInterval(() => showSlide(index + 1), 4000);
// ----------- 左右按钮事件 -----------
nextBtn.addEventListener('click', () => {
clearInterval(timer); // 点击按钮时先暂停自动轮播
showSlide(index + 1);
//重启自动轮播
timer = setInterval(() => showSlide(index + 1), 4000);
});
prevBtn.addEventListener('click', () => {
clearInterval(timer);
if (isAnimating) return; // 动画未结束,先阻止
// 如果在第一张,直接跳到克隆图(最后一张),再执行动画向前切换
if (index === 0) {
inner.style.transition = 'none';
index = total;
inner.style.transform = `translateX(-${index * 100}%)`;
// 用 setTimeout 确保下一帧再执行动画
setTimeout(() =>{
isAnimating = false; // 手动解锁
showSlide(index - 1), 20});
} else {
showSlide(index - 1);
}
//重启动画轮播
timer = setInterval(() => showSlide(index + 1), 4000);
});
// ----------- 底部圆点悬停事件 -----------
dots.forEach((dot, i) => {
dot.addEventListener('mouseover', () => { // mouseover 比 mouseenter 更稳
clearInterval(timer); //暂停自动轮播
showSlide(i); //切换对应幻灯片
timer = setInterval(() => showSlide(index + 1), 4000);
});
});
// ----------- 页面切后台再回来时同步状态 -----------
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
// 确保 transform 和动画锁状态正确
inner.style.transition = 'none';
inner.style.transform = `translateX(-${index * 100}%)`;
isAnimating = false; // 解除动画锁,不加这句等再切回来就会出bug
clearInterval(timer);
timer = setInterval(() => showSlide(index + 1), 4000);
} else {
// 页面切走时清理定时器
clearInterval(timer);
}
});
});
下拉刷新
明确什么是上拉下拉:就是手指在往上还是往下,手指往上拉,页面往下滚
主要是移动端会有这个功能。用事件监听,几个触摸时机:
touchstart:触摸周期开始,手指第一次触碰屏幕
touchmove:手指在屏幕上不断移动
touchend:手指离开屏幕,触摸周期结束
注意这里是一个“触摸周期”,一个用户在看网页时会有多次触摸周期
核心逻辑:
获取dom元素和变量准备:获取写好的刷新区域元素(需要设置height=0、overflow hidden,先隐藏起来)、商品部分元素
const refreshArea = document.querySelector('.refresh-area');
const content = document.querySelector('.list');
// 记录触摸起点和当前状态
let startY = 0; // 触摸起始位置
let distance = 0; // 下拉距离
let isPulling = false; // 是否在下拉中
let refreshing = false; // 是否正在刷新中
ispulling和refreshing的作用都是防止重复触发操作,类似防抖
touchstart时,判断当前滚动条window.scrollY是否在顶部(===0),如果不在顶部说明用户在浏览商品,则不做刷新操作,return(注意这里需要配合refreshing,如果正在刷新中也要直接return)
触摸界面那一刻,我们就当作用户在进行下拉操作,所以ispulling=true
// 监听触摸开始,触发时机:手指第一次接触屏幕时
window.addEventListener('touchstart', e => {
// 如果滚动条不在顶部,不触发刷新
// refreshing的作用是阻止刷新过程中再次刷新
if (window.scrollY !== 0 || refreshing) return;
startY = e.touches[0].clientY; // 记录起点
isPulling = true;
});
触摸事件可能有多个手指同时触屏,所以系统会把所有手指的位置存在一个数组里:e.touches
// 这是一个TouchList,里面每个手指都是一个 Touch 对象。
// 例如:e.touches[0] ,第一个手指
// e.touches[1] ,第二个手指(如果多指触控)
// 而 clientY 表示:当前手指相对于 视口顶部 的垂直坐标(单位是 px)
touchmove时,如果ispulling=false,说明用户并没有在下拉操作,此时不必执行,直接return;
记录下当前的clientY,计算distance = currentY - startY,如果distance小于0,说明用户在往上滑,return;
当distance到一定数值时改变刷新提示文字
// 监听触摸移动,触发时机:手指在屏幕上移动的整个过程,会持续触发
window.addEventListener('touchmove', e => {
if (!isPulling) return;
const currentY = e.touches[0].clientY;
distance = currentY - startY;
// 如果是向上滑(负值),直接忽略
if (distance < 0) return;
// 阻止浏览器默认滚动,让页面停下
e.preventDefault();
// 限制最大下拉距离,比如 100px
const pullDistance = Math.min(distance, 100);
// 根据距离调整刷新区域高度
refreshArea.style.height = pullDistance + 'px';
// 改变提示文字
if (pullDistance > 60) {
refreshArea.textContent = '释放刷新 ↑';
} else {
refreshArea.textContent = '↓ 下拉刷新';
}
});
touchend时,就是松开手指了,此时如果distance的距离超过设置的阈值,触发刷新函数triggerRefresh(),没有则隐藏,
重置distance
window.addEventListener('touchend', () => {
if (!isPulling) return;
isPulling = false;
// 如果下拉超过阈值(比如 60px),触发刷新
if (distance > 60) {
triggerRefresh();
} else {
// 没超过,回弹隐藏
refreshArea.style.height = '0px';
}
distance = 0;
});
刷新函数
刷新时执行,refreshing改为true,刷新区域的文字改成“正在刷新”,这里使用固定数据,setTimeout,来模拟刷新商品,可以改成请求接口
function triggerRefresh() {
refreshing = true;
refreshArea.textContent = '正在刷新...';
refreshArea.style.height = '50px'; // 固定显示高度
// 模拟异步加载(这里用 setTimeout 模拟)
setTimeout(() => {
// 模拟“刷新”商品
content.innerHTML = '';
for (let i = 1; i <= 5; i++) {
const item = document.createElement('div');
item.className = 'item';
item.textContent = '新商品 ' + i;
// 设置样式
item.style.padding = '25px';
item.style.fontSize = '20px';
item.style.minHeight = '80px';
content.appendChild(item);
}
// 显示完成提示
refreshArea.textContent = '刷新完成';
// window.loadedCount = content.children.length; // 已刷新商品数量
window.loadedCount = 0; // 从数组开头重新加载
window.loadingLock = false; // 解锁无限加载
// 稍等一下再收起
setTimeout(() => {
refreshArea.style.height = '0px';
refreshing = false;
}, 500);
}, 1000); // 模拟1秒网络延迟
}
window.loadedCount 和 window.loadingLock 是为了配合无限加载功能,如果是项目可以用状态管理方案
上拉无限加载
核心:对比scrollBottom和documentHeight
// 获取当前滚动位置 + 可视窗口高度(通常不变)
const scrollBottom = window.scrollY + window.innerHeight;
const documentHeight = document.documentElement.scrollHeight; // 整个文档高度,包括当前视口外的内容
// 如果接近底部,并且没有在加载
if (!window.loadingLock && scrollBottom >= documentHeight - 10) {
loadMoreItems();
}
scrollBottom=视口高度(通常是固定的,不会随滚动而变)+ 滚动条位置
documentHeight=展示内容的总高度,包括视口以外的区域
scrollBottom >= documentHeight - 10 代表快划到内容底部,需要触发加载函数了
也需要用状态锁
这里也是模拟商品,没有接口
const content = document.querySelector('.list');
const loading = document.querySelector('.loading');
// 假商品数组,每次加载 3 个
const allItems = ['商品6', '商品7', '商品8', '商品9', '商品10', '商品11', '商品12'];
window.loadingLock = false; //是否在加载
window.loadedCount = 0; //已经加载的内容数量
// 监听滚动事件
window.addEventListener('scroll', () => {
console.log('scrollY:', window.scrollY, 'innerHeight:', window.innerHeight);
// 获取当前滚动位置 + 可视窗口高度(通常不变)
const scrollBottom = window.scrollY + window.innerHeight;
const documentHeight = document.documentElement.scrollHeight; // 整个文档高度,包括当前视口外的内容
// 如果接近底部,并且没有在加载
if (!window.loadingLock && scrollBottom >= documentHeight - 10) {
loadMoreItems();
}
});
// 加载更多商品函数
function loadMoreItems() {
if (window.loadedCount >= allItems.length) return; // 没有更多了
window.loadingLock = true; // 加锁
loading.style.display = 'block'; // 显示 loading 提示
// 模拟网络延迟
setTimeout(() => {
const batch = allItems.slice(loadedCount, loadedCount + 3); // 每次加载 3 个
// name是数组中的当前元素
batch.forEach(name => {
//创建新的div元素
const div = document.createElement('div');
div.className = 'item'; //添加类名
div.textContent = name;
// 设置样式
div.style.padding = '25px';
div.style.fontSize = '20px';
div.style.minHeight = '80px';
content.appendChild(div);
});
loadedCount += batch.length; // 更新已加载数量
loading.style.display = 'none'; // 隐藏 loading
loadingLock = false; // 解锁
}, 800); // 模拟 0.8 秒加载
}
2237

被折叠的 条评论
为什么被折叠?



