跳到主要内容

js无缝轮播图

  主要的技术细节有:对象字面量,forEach传入回调函数,读取图片的高度以使浏览器强制渲染来无缝将图切换为最后或第一张图,定时器重复执行函数实现7000ms向右跳转一张图。

  具体实现步骤如下,(1.首先设置图片宽度为calc(1vw – 10px)减去浏览器滚动条的宽度让图片弹性铺平,并且使多余的图片溢出,然后定位两个左右箭头,均设置为绝对定位,其父元素设置为相对定位,使两个箭头相对于父元素定位,脱离正常文档流,然后分别用calc()函数设置其top和left(right)属性,定位到相应位置,最后设置cursor指针为pointer改变指针移入的样式。

  (2. 定位下方指示器(indicator),有几张图片就写几个span,对第一个span添加默认类为active以便指示是第几张图片。对于span元素应用样式,设置display为block,设置固定的宽高,绝对定位到相应的位置,设置border和border-radius使其变成空心圆,最后设置cursor为pointer。对于span .active类设置其背景为white即可为实心。

  (3. 开始书写js,我们首先实现非无缝的轮播图,首先用对象字面量定义doms,获取所有需要的元素,

let doms = {
    carouselList: document.querySelector(".carousel-list"),
    arrowLeft: document.querySelector(".carousel-arrow-left"),
    arrowRight: document.querySelector(".carousel-arrow-right"),
    indicators: document.querySelectorAll(".indicator span"),
};

  然后书写moveTo()函数,传入一个index用来指示传到第几张图,对于doms的carouselList属性设置其样式transform,translateX(-100%)为向右平移一个图片的宽度,第几张图就是百分之多少,所以使用模板字符串。然后添加一个0.5s的过渡。去掉指示器中的选中效果,具体为选择span.active元素,classList.remove(“active”),然后对于对应的图片添加active类。

function moveTo(index) {
    doms.carouselList.style.transform = `translateX(-${index}00%)`;
    doms.carouselList.style.transition = ".5s";
    // 去掉指示器的选中效果
    let active = document.querySelector(".indicator span.active");
    active.classList.remove("active");
    // 添加选中的指示器
    doms.indicators[index].classList.add("active");

    curIndex = index;
}

  然后我们对每一个span元素添加监听事件即可实现点击跳转,具体使用forEach方法,传入回调函数返回item和i,对于每一个onclick设置一个匿名函数,函数内容为调用moveTo(i)跳转到对应的第几章图片。

doms.indicators.forEach(function(item, i) {
    item.onclick = function() {
        moveTo(i);
    }
})

  至此简单的非无缝轮播图大致实现,下面我们实现无缝轮播图。

  无缝轮播图的实现思路比较简单,就是将最后一张图放到第一张的前面,第一张图放到最后一张的后面。当跳转到最后一张图再往后跳转时取消过渡瞬间移动到最前面那张图,然后添加过渡,再跳转到第一张,相反同理。

  所以我们首先需要复制第一张图和最后一张图分别放到末尾和开始并且设置其定位。我们写一个函数init(),在这我们对doms.carouselList对象使用firstElementChild方法返回一个对象,再使用cloneNode()方法设置其函数参数为true,来克隆其节点以及其所有后代。赋值最后一张图同理,然后分别使用appendChild方法和insertBefore方法分别插入两张图到末尾和第一张。最后设置两张图绝对定位。并让last所复制的最后一张图向左平移100%以移动到第一张图前面。最后调用函数。

function init() {
    // 复制第一张图
    let first = doms.carouselList.firstElementChild.cloneNode(true);
    // 复制最后一张图
    let last = doms.carouselList.lastElementChild.cloneNode(true);
    // 将第一张图放到末尾
    doms.carouselList.appendChild(first);
    // 将最后一张图放到第一张
    doms.carouselList.insertBefore(last, doms.carouselList.firstElementChild);
    // 设置最后一张复制图为绝对定位
    last.style.position = "absolute";
    last.style.transform = "translateX(-100%)";
}

init();

  然后我们实现跳转的关键部分,向右跳转和向左跳转,分别写两个函数rightNext()和leftNext(),我们首先书写rightNext()函数,对于是否是最后一张图进行判断,如果是我们就设置carouselList的transform样式为translateX(100%)设置为我们最前面的复制好的最后一张图(注意浏览器的x轴和y轴是在最左上角分别向右和向下是正方向,所以是正的100%),并且设置transition过渡为none,最后调用moveTo(0)函数跳到第一张。但是此时我们发现在不想应用过渡的时候(实现无缝部分,即以迅雷不及掩耳之速跳转到第一张图的部分)并没有清楚过渡,这是因为浏览器渲染原理,清除过渡了之后调用moveTo(0)函数又添加了过渡,浏览器还来不及渲染,所以我们需要对那一部分进行一次强制渲染,所以写一句很神经的代码来读取他的高度,因为读取高度的操作会强制浏览器渲染。至此if(true)的部分实现完毕。如果不是最后一张图的话就正常跳到下一张图。最后为了方便的记录是第几章图,我们定义一个变量curIndex并初始化为0,对于每一次调用moveTo(index)函数都将其index(跳到第几张图)赋值给curIndex来记录是第几章图,并且与active的span对应上。至此我们向右跳转的函数实现完毕,也解释了上述moveTo()函数中的curIndex变量是怎么回事。

function rightNext() {
    if(curIndex === count - 1) {
        doms.carouselList.style.transform = "translateX(100%)";
        doms.carouselList.style.transition = "none";
        // 强制渲染
        doms.carouselList.clientHeight;
       
        moveTo(0);
    } else {
        moveTo(curIndex + 1);
    }
}

  leftNext()同理,只不过我们需要注意translateX的x轴方向,注意其值。

  对了!为了方便的添加和删除图片,我们定义一个软编码count变量,其值为指示器的长度,以便我们的增添删减。

  最后对两个箭头添加事件即可。并设置定时器。

function leftNext() {
    if(curIndex === 0) {
        doms.carouselList.style.transform = `translateX(-${count}00%)`;
        doms.carouselList.style.transition = "none";
        // 强制渲染
        doms.carouselList.clientHeight;
        moveTo(count - 1);
    } else {
        moveTo(curIndex - 1);
    }
}

let count = doms.indicators.length;

function rightNext() {
    if(curIndex === count - 1) {
        doms.carouselList.style.transform = "translateX(100%)";
        doms.carouselList.style.transition = "none";
        // 强制渲染
        doms.carouselList.clientHeight;

        moveTo(0);
    } else {
        moveTo(curIndex + 1);
    }
}


doms.arrowLeft.onclick = leftNext;
doms.arrowRight.onclick = rightNext;

setInterval(() => {
    rightNext();
}, 7000);

  至此js无缝轮播图的所有实现逻辑和细节叙述完毕。

完整代码:

let doms = {
    carouselList: document.querySelector(".carousel-list"),
    arrowLeft: document.querySelector(".carousel-arrow-left"),
    arrowRight: document.querySelector(".carousel-arrow-right"),
    indicators: document.querySelectorAll(".indicator span"),
};

let curIndex = 0;

function moveTo(index) {
    doms.carouselList.style.transform = `translateX(-${index}00%)`;
    doms.carouselList.style.transition = ".5s";
    // 去掉指示器的选中效果
    let active = document.querySelector(".indicator span.active");
    active.classList.remove("active");
    // 添加选中的指示器
    doms.indicators[index].classList.add("active");
    curIndex = index;
}


doms.indicators.forEach(function(item, i) {
    item.onclick = function() {
        moveTo(i);
    }
})


function init() {
    // 复制第一张图
    let first = doms.carouselList.firstElementChild.cloneNode(true);
    // 复制最后一张图
    let last = doms.carouselList.lastElementChild.cloneNode(true);
    // 将第一张图放到末尾
    doms.carouselList.appendChild(first);
    // 将最后一张图放到第一张
    doms.carouselList.insertBefore(last, doms.carouselList.firstElementChild);
    // 设置最后一张复制图为绝对定位
    last.style.position = "absolute";
    last.style.transform = "translateX(-100%)";
}

init();

function leftNext() {
    if(curIndex === 0) {
        doms.carouselList.style.transform = `translateX(-${count}00%)`;
        doms.carouselList.style.transition = "none";
        // 强制渲染
        doms.carouselList.clientHeight;
        moveTo(count - 1);
    } else {
        moveTo(curIndex - 1);
    }
}

let count = doms.indicators.length;

function rightNext() {
    if(curIndex === count - 1) {
        doms.carouselList.style.transform = "translateX(100%)";
        doms.carouselList.style.transition = "none";
        // 强制渲染
        doms.carouselList.clientHeight;

        moveTo(0);
    } else {
        moveTo(curIndex + 1);
    }
}


doms.arrowLeft.onclick = leftNext;
doms.arrowRight.onclick = rightNext;


setInterval(() => {
    rightNext();
}, 7000);