要求
- 图片自行滚动(规定自左向右滚动)
- 点击左右箭头,实现图片翻页;
- 点击提示圆点,显示不同图片;
- 滚动、翻页和显示都需要过渡效果;
- 响应式:轮播图随着浏览器窗口大小变化而变化;
- 功能整合。
最终效果
整体思路
之前也写过轮播图,那时候是WEB里的一个js作业,懵了半天,最后靠自己独立写出来了,不过思路不是很清晰,有点浆糊的感觉。而这一次是组队PROJECT,要我写一个页面的全屏轮播并响应浏览器大小
。
之前也搜索了其他人写的轮播图,参考了一些网站的轮播是怎么根据浏览器窗口大小响应的,发现可以从最基本的点击事件
出发:
左右点击翻页是最基本的操作
,封装
到一个next_pic()函数
里,定时器可以周期执行这个函数来实现自动轮播
,而显示提示点以及提示点和当前图片的联系就小菜一碟了。
具体实现
HTML文档结构
<!-- 轮播图 -->
<div id="content">
<div id="carousel_wrap">
<div id="carousel_images">
<!-- 前后分别加上一张图片,方便无缝过渡显示。可以使用JS DOM增加节点操作省去该步骤 -->
<img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNGoZt.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
</div>
<span class="arrow left-arrow"><</span>
<span class="arrow right-arrow">></span>
<div id="dots">
<!-- 使用小点标记实际多少张图片,要添加图片时需要修改carousel_images和此处 -->
<span class="dot active"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
<div id="test">
<h1>blog</h1>
<h1>blogs</h1>
<h1>blog</h1>
<h1>blog</h1>
</div>
</div>
<!-- END 轮播图 -->
CSS样式
将“装着”轮播图片的carousel_images中的white-space属性设置为:nowrap
,意为当到达容器边界时不换行,配合overflow
实现carousel_wrap之外的图片都隐藏掉。
这里提到white-space
属性:用来设置element元素对内容中的空格的处理方式
,有着几个可选值:normal,nowrap,pre,pre-wrap,pre-line。没有设置white-space属性,则默认为white-space:normal。
区别这几个属性值关键词:源码空格
、源码换行
、容器大小换行
。源码换行分为(1)br标签换行和(2)源码换行符换行,而(1)是全都符合的,所以区别就在于源码中的换行是否有效,容器大小换行就是根据容器自适应换行。因此在关键一点就变成->合并/保留空格、换行/不换行
。
-
normal
表示合并空格、换行
,多个相邻空格合并成一个空格,在源码中的换行
作为空格处理,只会根据容器的大小
进行自动换行
。 -
nowrap
也合并空格、不换行
,在源码中的换行
作为空格处理,但是不会根据容器大小换行,表示不换行
。
常与overflow配合使用,如:
overflow: hidden;
text-overflow: ellipsis; 表示文本省略号
pre
表示保留空格、不换行
,保持源码中的空格,有几个空格算几个空格显示,同时换行只认源码中的换行和br标签,不会去自适应容器换行。
也就是说pre中的文本在源码中是什么样子,到网页中就是什么样子。
-
pre-wrap
表示保留空格、换行
,保留空格,并且除了碰到源码中的换行和
会换行外,还会自适应容器
的边界进行换行。 -
pre-line
比较特殊,表示合并空格、换行
,合并空格,换行和white-space:pre-wrap一样,遇到源码中的换行和br会换行,碰到容器的边界也会换行。
white-space:pre-line会保留源码中的换行
,但是不会保留源码中的空格
。
回到轮播当中,发现使用了nowrap之后,图片之间有间隙,可以使用font-size: 0;
清除缝隙。
body {
overflow: scroll;
}
/* 轮播图样式表 */
#content #carousel_wrap {
position: relative;
margin: 0 auto;
width: 100%; /* 轮播图宽度 */
overflow: hidden;
}
#content #carousel_wrap #carousel_images {
position: absolute;
border: 0;
outline: none;
white-space: nowrap; /* 将图片一行排列 */
width: 100%;
font-size: 0; /* 清除white-space间隙 */
margin: 0px;
}
#content #carousel_wrap #carousel_images img {
width: 100%;
}
#content #carousel_wrap .arrow {
position: absolute;
font-weight: bold;
font-size: 50px;
color: lightgray;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
transition-property: opacity;
transition-duration: 0.5s;
}
#content #carousel_wrap .arrow:hover {
opacity: 0.5;
}
#content #carousel_wrap .left-arrow {
left: 20px;
}
#content #carousel_wrap .right-arrow {
right: 20px;
}
#content #carousel_wrap #dots {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%)
}
#content #carousel_wrap .dot {
background-color: white;
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin: 4px;
opacity: 0.2;
cursor: pointer;
}
#content #carousel_wrap .active {
opacity: 1;
}
.transition {
transition-property: left;
transition-duration: 1s;
}
/* END 轮播图样式表 */
JS
有了好的设计思路,轮播图的JS就不难写了。
DOM操作定位到具体元素:
var carouImg = document.getElementById("carousel_images");
var carouWrap = document.getElementById("carousel_wrap");
var img = carouImg.getElementsByTagName("img")[0];
var leftArrow = document.getElementsByClassName("left-arrow")[0];
var rightArrow = document.getElementsByClassName("right-arrow")[0];
var oBtn = document.getElementsByClassName("dot");
var index = 0; //标记当前图片
var index_length = oBtn.length; //图片数量
响应浏览器
为了能响应浏览器的大小
来全屏显示轮播图片,让图片的宽占据浏览器宽的百分比值,图片的大小不定,那么轮播判断位置的时候就要使用clientWidth
获取宽度。
关于响应式,要实现响应式,关键要对浏览器窗口的变化做出监听,使用onresize
事件监听浏览器窗口变化,使用offsetWidth获取窗口的宽度。
同时为了代码的通用性
,方便以后添加图片,不要把图片总数量的确切数值写在代码里,可以用提示点代表一张图片,用length获取总数。
涉及临界情况时,要考虑图片的位置以及过渡效果的影响。
动态获取绝对定位轮播图的高度,设置carousel_wrap的高度,宽度为整个main宽度。监听body大小变化,修改轮播图的图片位置和高度:
// 动态获取绝对定位轮播图的高度,设置carousel_wrap的高度,宽度为整个main宽度
// 如果mystyle.css中使用overflow:auto->含有滚动条宽度; 故使用overflow:scroll
carouImg.style.left = -img.clientWidth + "px";
console.log(carouImg.style.left);
carouWrap.style.height = img.offsetHeight + "px";
// 监听body大小变化,修改轮播图的图片位置和高度
document.body.onresize = function () {
carouImg.style.left = -img.clientWidth + "px";
carouWrap.style.height = img.offsetHeight + "px";
}
点击左右箭头翻页
next_pic()函数:
// 下一张图片
function next_pic() {
var left = parseInt(carouImg.style.left);
if (left <= (-img.clientWidth) * (index_length + 1)) {
// 临界情况判断
carouImg.classList.remove("transition");
var newLeft = -img.clientWidth * 1;
carouImg.style.left = newLeft + 'px';
newLeft = -img.clientWidth * 2;
carouImg.classList.add("transition");
index = 1;
}
else {
// 一般情况
var newLeft = parseInt(carouImg.style.left) - img.clientWidth;
(index == (index_length - 1)) ? index = 0 : index += 1;
}
carouImg.style.left = newLeft + 'px'; // 不要忘记添加'px'
console.log(newLeft);
}
pre_pic()函数:
// 上一张图片
function pre_pic() {
var left = parseInt(carouImg.style.left);
if (left >= -10) {
// 临界情况判断
carouImg.classList.remove("transition");
var newLeft = -img.clientWidth * index_length;
carouImg.style.left = newLeft + 'px';
newLeft = -img.clientWidth * (index_length - 1);
carouImg.classList.add("transition");
index = index_length - 2;
}
else {
// 一般情况
var newLeft = parseInt(carouImg.style.left) + img.clientWidth;
(index == 0) ? index = (index_length - 1) : index -= 1;
}
carouImg.style.left = newLeft + 'px';
console.log(newLeft);
}
显示提示点
通过对CSS类的增删操作
来实现提示点样式的变化。
function showCurrentDot(index) {
for (let i = 0; i < oBtn.length; ++ i) {
(i == index) ? oBtn[i].classList.add("active") : oBtn[i].classList.remove("active");
}
}
无缝过渡
同样通过对类的增删操作
,用transition属性
实现过渡动画效果。
// 给图片添加过渡效果
carouImg.classList.add("transition");
定时器自动翻转
设置定时器
实现自动轮播,添加鼠标移入移出监听事件
。
// 设置轮播定时器
var timer = setInterval(function(){
next_pic();
showCurrentDot(index);
}, 3000);
carouWrap.onmouseover = function() {
clearInterval(timer);
}
carouWrap.onmouseout = function() {
timer = setInterval(function () {
next_pic();
showCurrentDot(index);
}, 3000);
}
代码整合
html
{% fold %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Lifeblog.com</title>
<script type="text/javascript" src="js/friends.js"></script>
<link rel="stylesheet" type="text/css" href="css/friends.css" />
</head>
<body>
<!-- 轮播图 -->
<div id="content">
<div id="carousel_wrap">
<div id="carousel_images">
<!-- 前后分别加上一张图片,方便无缝过渡显示。可以使用JS DOM增加节点操作省去该步骤 -->
<img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNGoZt.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
<img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
</div>
<span class="arrow left-arrow"><</span>
<span class="arrow right-arrow">></span>
<div id="dots">
<!-- 使用小点标记实际多少张图片,要添加图片时需要修改carousel_images和此处 -->
<span class="dot active"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
<div id="test">
<h1>blog</h1>
<h1>blogs</h1>
<h1>blog</h1>
<h1>blog</h1>
</div>
</div>
<!-- END 轮播图 -->
</body>
</html>
{% endfold %}
CSS
{% fold %}
body {
overflow: scroll;
}
/* 轮播图样式表 */
#content #carousel_wrap {
position: relative;
margin: 0 auto;
width: 100%; /* 轮播图宽度 */
overflow: hidden;
}
#content #carousel_wrap #carousel_images {
position: absolute;
border: 0;
outline: none;
white-space: nowrap; /* 将图片一行排列 */
width: 100%;
font-size: 0; /* 清除white-space间隙 */
margin: 0px;
}
#content #carousel_wrap #carousel_images img {
width: 100%;
}
#content #carousel_wrap .arrow {
position: absolute;
font-weight: bold;
font-size: 50px;
color: lightgray;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
transition-property: opacity;
transition-duration: 0.5s;
}
#content #carousel_wrap .arrow:hover {
opacity: 0.5;
}
#content #carousel_wrap .left-arrow {
left: 20px;
}
#content #carousel_wrap .right-arrow {
right: 20px;
}
#content #carousel_wrap #dots {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%)
}
#content #carousel_wrap .dot {
background-color: white;
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin: 4px;
opacity: 0.2;
cursor: pointer;
}
#content #carousel_wrap .active {
opacity: 1;
}
.transition {
transition-property: left;
transition-duration: 1s;
}
/* END 轮播图样式表 */
{% endfold %}
JS
{% fold %}
window.onload = function() {
var carouImg = document.getElementById("carousel_images");
var carouWrap = document.getElementById("carousel_wrap");
var img = carouImg.getElementsByTagName("img")[0];
var leftArrow = document.getElementsByClassName("left-arrow")[0];
var rightArrow = document.getElementsByClassName("right-arrow")[0];
var oBtn = document.getElementsByClassName("dot");
var index = 0;
var index_length = oBtn.length;
// 给图片添加过渡效果
carouImg.classList.add("transition");
// 动态获取绝对定位轮播图的高度,设置carousel_wrap的高度,宽度为整个main宽度
// 如果mystyle.css中使用overflow:auto->含有滚动条宽度; 故使用overflow:scroll
carouImg.style.left = -img.clientWidth + "px";
console.log(carouImg.style.left);
carouWrap.style.height = img.offsetHeight + "px";
// 监听body大小变化,修改轮播图的图片位置和高度
document.body.onresize = function () {
carouImg.style.left = -img.clientWidth + "px";
carouWrap.style.height = img.offsetHeight + "px";
}
// 点击右箭头
rightArrow.onclick = function() {
next_pic();
showCurrentDot(index);
}
// 点击左箭头
leftArrow.onclick = function () {
pre_pic();
showCurrentDot(index);
}
// 点击小点
for (let i = 0; i < oBtn.length; ++ i) {
oBtn[i].onclick = function() {
var newLeft = (-img.clientWidth) * (i + 1);
carouImg.style.left = newLeft + 'px';
console.log(i);
showCurrentDot(i);
}
}
// 下一张图片
function next_pic() {
var left = parseInt(carouImg.style.left);
if (left <= (-img.clientWidth) * (index_length + 1)) {
// 临界情况判断
carouImg.classList.remove("transition");
var newLeft = -img.clientWidth * 1;
carouImg.style.left = newLeft + 'px';
newLeft = -img.clientWidth * 2;
carouImg.classList.add("transition");
index = 1;
}
else {
// 一般情况
var newLeft = parseInt(carouImg.style.left) - img.clientWidth;
(index == (index_length - 1)) ? index = 0 : index += 1;
}
carouImg.style.left = newLeft + 'px'; // 不要忘记添加'px'
console.log(newLeft);
}
// 上一张图片
function pre_pic() {
var left = parseInt(carouImg.style.left);
if (left >= -10) {
// 临界情况判断
carouImg.classList.remove("transition");
var newLeft = -img.clientWidth * index_length;
carouImg.style.left = newLeft + 'px';
newLeft = -img.clientWidth * (index_length - 1);
carouImg.classList.add("transition");
index = index_length - 2;
}
else {
// 一般情况
var newLeft = parseInt(carouImg.style.left) + img.clientWidth;
(index == 0) ? index = (index_length - 1) : index -= 1;
}
carouImg.style.left = newLeft + 'px';
console.log(newLeft);
}
function showCurrentDot(index) {
for (let i = 0; i < oBtn.length; ++ i) {
(i == index) ? oBtn[i].classList.add("active") : oBtn[i].classList.remove("active");
}
}
// 设置轮播定时器
var timer = setInterval(function(){
next_pic();
showCurrentDot(index);
}, 3000);
carouWrap.onmouseover = function() {
clearInterval(timer);
}
carouWrap.onmouseout = function() {
timer = setInterval(function () {
next_pic();
showCurrentDot(index);
}, 3000);
}
}
{% endfold %}
思考和总结
总的来说,这一次的轮播图实现,自己还是比较满意的,从代码的优化通用性和逻辑的清晰度来说,都比之前写的那次轮播图好多了,更不用说这一次的轮播图的实现难度比上次的要大,这次的轮播要求是响应式而不是定长的,这么想想自己还是挺高兴的。
不过也有不足,本以为是“完美”的轮播了,但是还是有十分小的bug:进入第一张和最后一张图片的补充替补图片时,点击提示点,过渡是按照替补图片而不是正式图片过渡的,这里是不足的地方。
寻思着可以通过index来区分正式和替补图片,然后消除过渡,把正式图片和替补图片互换,再开启过渡效果,这样就可以解决这个小bug了。
不过最近时间很紧,小作业大作业好多,日后有机会再修改成“完美”的代码。
更新
更新:解决了上面提到的bug,思路就是总结里提到的,在关键地方清除过渡,修改图片位置,再添加过渡。
具体做法:在点击提示点的代码中修改。
注意click事件里left值修改完毕再添加transition过渡,如果只是修改到实际图片位置,但是没有修改本次点击图片位置就添加transition,bug依然存在,这里没有想明白,难道是因为click事件点击和松开鼠标这个过程的某种原理吗?
for (let i = 0; i < oBtn.length; ++ i) {
oBtn[i].onclick = function() {
var left = parseInt(carouImg.style.left);
var newLeft;
// 如果没有临界判断,当图片位于“替补图片”时,点击提示点会有错乱过渡
if (left <= (-img.clientWidth) * (index_length + 1)) {
// 临界情况判断
carouImg.classList.remove("transition");
newLeft = -img.clientWidth * 1;
carouImg.style.left = newLeft + 'px';
}
if (left >= -10) {
// 临界情况判断
carouImg.classList.remove("transition");
newLeft = -img.clientWidth * index_length;
carouImg.style.left = newLeft + 'px';
}
newLeft = (-img.clientWidth) * (i + 1);
carouImg.style.left = newLeft + 'px';
// 注意click事件的执行过程,要在修改完left后添加transition类
carouImg.classList.add("transition");
index = i;
showCurrentDot(i);
}
}