原生js实现图片轮播组件开发

轮播图

功能需求:

  • 默认每间隔3s,图片自动轮播一次;
  • 点击左右按钮,实现图片的无缝轮播;
  • 点击下方小圆点,实现图片的无缝轮播;
  • 可以自定义各轮播图尺寸;
  • 当缩放浏览器窗口时,轮播图尺寸随之改变;

看一下效果:
原生js实现图片轮播组件开发
分析:

  • 为了有更好的效果,将图片预加载完成后,再进行图片轮播;
  • 因为使用插入css样式来写,Carousel.styles 只执行一次,不能实现多个不同尺寸的轮播图,所以轮播图的宽高需要单独设置;
  • 使用了事件委托机制,所有点击事件的目标对象都是最外层的div,当抛发事件时,不能手动修改 e.target 的值,否则会出错;
  • 要设置好图片是否正在移动和图片是否开始自动轮播的两个变量的默认值;

下面附上代码:

html结构代码,实例化两个轮播图;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>carousel</title>
</head>
<body>
    <script type="module">
        import Rem from './js/Rem.js';
        import Carousel from './js/Carousel.js';
        var arr=["a.png","b.png","c.png","d.png","e.png"];
        init();
        function init(){
            Rem.init();
            let c1 = new Carousel(arr,"./image/");
            c1.setSize(600,200);
            c1.appendTo("body");
            let c2=new Carousel(arr,"./image/");
            c2.appendTo("body")
        }
    </script>
</body>
</html>

Carousel.js文件:

import Utils from "./Utils.js";
import Rem from "./Rem.js";
export default class Carousel{
    clientWidth;
    clientHeight;
    position=0;//当前图片的索引
    direction="left";//imgcon移动的方向
    speed=30;//imgcon移动的速度
    x=0;//imgcon的left值
    prepLi;//表示上一个小圆点
    moveBool=false;//控制imgcon是否移动
    autoBool=true;//控制是否自动轮播
    timer=360;//图片停留时间
    static styles=false;
    constructor(_list,basePath){
        //创建外层的div
        this.elem=this.createE();
        //图片预加载
        Utils.loadImg({list:_list,basePath:basePath,callback:_list=>this.finishImg(_list)});
        //执行动画
        this.animation();
    }
    finishImg(resultList){
        //设置图片默认大小
        resultList.forEach(item=>{
            item.style.width=this.clientWidth+"rem";
            item.style.height=this.clientHeight+"rem";
        })
        //将加载完的图片赋值给this.imgList
        this.imgList=resultList;
        //创建轮播内容
        this.createCarousel();
    }
    createE(){
        if(this.elem) return this.elem;
        //创建外层容器
        let div=Utils.createE("div");
        div.className="carouselContainer";
        //给外层父元素监听点击事件
        div.addEventListener("click",e=>this.clickHandler(e));
        //事件捕获阶段监听鼠标滑入滑出事件
        div.addEventListener("mouseenter",e=>this.mouseHandler(e),true);
        div.addEventListener("mouseout",e=>this.mouseHandler(e),true);
        return div;
    }
    appendTo(parent){
        Utils.appendTo(this.elem,parent);
        if(parent.constructor===String) parent=document.querySelector(parent);
        //如果有传进来的宽高值,则使用传进来的值
        if(this.w) return;
        // 轮播图默认为父容器的宽高
        this.clientWidth=Rem.getRem(parent.offsetWidth);
        this.clientHeight=Rem.getRem(parent.offsetWidth/3);
    }
    setSize(w,h){
        this.w=w;
        //单独设置各个轮播图尺寸
        this.clientWidth=Rem.getRem(w);
        this.clientHeight=Rem.getRem(h);
    }
    createCarousel(){
        this.elem.innerHTML=`<div id="imgCon" class="carouselImgCon"></div>
        <img class="carouselBtn" id="leftBtn" src="./image/left.png">
        <img class="carouselBtn" id="rightBtn" src="./image/right.png">
        ${this.createList()}`;
        //设置样式
        Carousel.setStyles();
        //获取元素
        Utils.getIdElem(this.elem,this);
        //默认显示第一张图片
        Utils.appendTo(this.imgList[0],this.imgCon);
        //获取下方的Li列表,转化成数组格式
        this.carouselIconList=Array.prototype.slice.call(this.carouselIcon.children);
        //设置this.prepLi默认为第一个li
        this.prepLi=this.carouselIconList[this.position];
        //设置动态样式,因为Carousel.styles只执行一次,不能实现多个不同尺寸的轮播图,所以宽高需要单独设置
        this.setDynamicStyle();
    }
    createList(){
        //根据数组的长度,创建下方的list图标
        let str=`<ul id="carouselIcon" class="carouselIcon">`;
        for(let i=0;i<this.imgList.length;i++){
            str+=`<li ${i===0?'class="active"':''}></li>`;
        }
        str+=`</ul>`;
        return str;
    }
    clickHandler(e){
        //如果正在轮播,则直接跳出
        if(this.moveBool) return;
        //将按钮的id赋值给btnTxt变量
        let btnTxt=e.target.id;
        //autoMove的值,是抛发时传过来的,如果有值,则触发右侧按钮点击事件
        if(e.autoMove) btnTxt=e.autoMove;
        //如果点击的元素不是左右按钮和下方的li,则直接退出
        if(!/btn/i.test(btnTxt)&&e.target.nodeName!=="LI") return;
        if(btnTxt==="rightBtn"){
            //如果点击的是右边的按钮,imgCon向左移动
            this.direction="left";
            //图片索引值加1
            this.position++;
            if(this.position>this.imgList.length-1) this.position=0;
        }else if(btnTxt==="leftBtn"){
            //如果点击的是左边的按钮,imgCon向右移动
            this.direction="right";
            //图片索引值减1
            this.position--;
            if(this.position<0) this.position=this.imgList.length-1;
        }else if(e.target.nodeName==="LI"){
            //获取上一个图片的索引
            let preIndex=Array.from(e.target.parentElement.children).indexOf(this.prepLi);
            //获取到当前点击的li的索引
            let nowIndex=Array.from(e.target.parentElement.children).indexOf(e.target);
            if(preIndex==nowIndex) return;
            //如果当前点击Li的索引大于上一个图片的索引,相当于点击右边按钮,imgCon向左移动
            if(preIndex<nowIndex) this.direction="left";
            else this.direction="right";
            //将当前点击li的索引赋值给this.position
            this.position=nowIndex;
        }
        //创建下一张图片
        this.createNextImg();
    }
    createNextImg(){
        //创建图片
        let img=this.imgList[this.position];
        if(this.direction==="left"){
            //如果是向左移动,创建的图片添加到最后
            Utils.appendTo(img,this.imgCon);
            this.x=0;
        }else{
            //如果是向右移动,创建的图片添加到最前面
            Utils.insertBefore(img,this.imgCon);
            this.x=-this.clientWidth;
        }
        this.imgCon.style.left=this.x+"rem";
        //moveBool设为true
        this.moveBool=true;
        //改变下方小圆点的样式
        this.changeListState();
    }
    animation(){
        //执行轮播动画
        requestAnimationFrame(()=>this.animation());
        this.update();
    }
    update(){
        this.ImgConMove();
        this.autoImgConMove();
    }
    ImgConMove(){
        //如果moveBool为false,直接跳出
        if(!this.moveBool) return;
        if(this.direction==="left"){
            //如果向左移动,left值-=speed
            this.x-=Rem.getRem(this.speed);
            if(this.x<=-this.clientWidth){
                //当完成一次轮播后,将前面的图片移除,imgcon的left值设为0,moveBool设为false
                this.imgCon.firstElementChild.remove();
                this.x=0;
                this.moveBool=false;
            }
        }else{
            //如果向右移动,left值+=speed
            this.x+=Rem.getRem(this.speed);
            if(this.x>=0){
                //当完成一次轮播后,将后面的图片移除,imgcon的left值设为0,moveBool设为false
                this.x=0;
                this.imgCon.lastElementChild.remove();
                this.moveBool=false;
            }
        }
        this.imgCon.style.left=this.x+"rem";
    }
    autoImgConMove(){
        //自动轮播,如果autoBool为false,则直接跳出
        if(!this.autoBool) return;
        //每隔一段时间后,抛发一次事件
        this.timer--;
        if(this.timer>0) return;
        this.timer=360;
        //抛发事件,将右侧按钮的id传过去
        let evt=new Event("click");
        evt.autoMove="rightBtn";
        this.elem.dispatchEvent(evt);
    }
    mouseHandler(e){
        //鼠标移入,停止自动轮播
        if(e.type==="mouseenter"){
            this.autoBool=false;
            this.timer=360;
        }else{
            this.autoBool=true;
        }
    }
    changeListState(){
        //将上一个小圆点移除active的样式
        if(this.prepLi) Utils.removeClass(this.prepLi,"active");
        //将当前的小圆点赋值给this.prepLi
        this.prepLi=this.carouselIconList[this.position];
        //给当前的小圆点添加active的样式
        Utils.addClass(this.prepLi,"active");
    }
    setDynamicStyle(){
        //设置轮播图容器尺寸
        Object.assign(this.elem.style,{
            width:this.clientWidth+"rem",
            height:this.clientHeight+"rem"
        })
        //设置Imgcon尺寸
        Object.assign(this.imgCon.style,{
            width:this.clientWidth*2+"rem",
            height:this.clientHeight+"rem",
        })
        //设置左右按钮的top值
        let btnHeight=this.leftBtn.offsetHeight;
        Object.assign(this.leftBtn.style,{
            top:(this.clientHeight-Rem.getRem(btnHeight))/2+"rem",
        })
        Object.assign(this.rightBtn.style,{
            top:(this.clientHeight-Rem.getRem(btnHeight))/2+"rem",
        })
        //设置小圆点列表的left值
        let ulWidth=0;
        this.carouselIconList.forEach(item=>{
            ulWidth+=item.offsetWidth+parseInt(getComputedStyle(item).marginRight);
        })
        Object.assign(this.carouselIcon.style,{
            left:(this.clientWidth-Rem.getRem(ulWidth))/2+"rem"
        })
    }
    static setStyles(){
        if(Carousel.styles) return;
        Carousel.styles=true;
        Utils.insertCss(".carouselContainer",{
            position:"relative",
            overflow:"hidden"
        })
        Utils.insertCss(".carouselImgCon",{
            position:"absolute",
            left:"0rem",
            top:"0rem",
        })
        Utils.insertCss(".carouselBtn",{
            width:Rem.getRem(30)+"rem",
            height:Rem.getRem(60)+"rem",
            position:"absolute",
            cursor:"pointer"
        })
        Utils.insertCss("#leftBtn",{
            left:Rem.getRem(20)+"rem"
        })
        Utils.insertCss("#rightBtn",{
            right:Rem.getRem(20)+"rem"
        })
        Utils.insertCss(".carouselIcon",{
            listStyle:"none",
            padding:"0px",
            margin:"0px",
            position:"absolute",
            bottom:Rem.getRem(20)+"rem",
        })
        Utils.insertCss(".carouselIcon li",{
            width:Rem.getRem(20)+"rem",
            height:Rem.getRem(20)+"rem",
            float:"left",
            marginRight:Rem.getRem(20)+"rem",
            backgroundColor:"rgba(255,0,0,.4)",
            borderRadius:"50%",
            cursor:"pointer"
        })
        Utils.insertCss(".carouselIcon li:last-child",{
            marginRight:"0rem"
        })
        Utils.insertCss(".carouselIcon li.active",{
            backgroundColor:"rgba(255,0,0,1)"
        })
    }
}

Rem.js文件,用来监听浏览器窗口缩放,进行px和rem之间的单位转换:

export default class Rem {
    static init(){
        Rem.defaultStyle();
        Rem.resizeHandler();
        window.addEventListener("resize", Rem.resizeHandler);
    }
    static resizeHandler() {
        let contW = Math.floor(document.documentElement.clientWidth);
        let fontS = (contW / screen.width) * 100;
        document.documentElement.style.fontSize = fontS + "px";
    }
    static defaultStyle(){
        document.documentElement.style.fontSize = "100px";
        document.documentElement.style.height = "100%";
        document.body.style.height = "100%";
        document.body.style.fontSize = "16px";
        document.body.style.padding = "0";
        document.body.style.margin = "0";
    }
    static getRem(size) {
        var fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
        return size / fontSize;
    }
}

Utils.js文件,是一个工具包文件:

export default class Utils{
    static createE(elem,style,prep){
        elem=document.createElement(elem);
        if(style) for(let prop in style) elem.style[prop]=style[prop];
        if(prep) for(let prop in prep) elem[prop]=prep[prop];
        return elem;
    }
    static appendTo(elem,parent){
        if (parent.constructor === String) parent = document.querySelector(parent);
        parent.appendChild(elem);
    }
    static insertBefore(elem,parent){
        if(parent.constructor === String) parent=document.querySelector(parent);
        parent.insertBefore(elem,parent.firstElementChild);
    }
    static randomNum(min,max){
        return Math.floor(Math.random*(max-min)+min);
    }
    static randomColor(alpha){
        alpha=alpha||Math.random().toFixed(1);
        if(isNaN(alpha)) alpha=1;
        if(alpha>1) alpha=1;
        if(alpha<0) alpha=0;
        let col="rgba(";
        for(let i=0;i<3;i++){
            col+=Utils.randomNum(0,256)+",";
        }
        col+=alpha+")";
        return col;
    }
    static insertCss(select,styles){
        if(document.styleSheets.length===0){
            let styleS=Utils.createE("style");
            Utils.appendTo(styleS,document.head);
        }
        let styleSheet=document.styleSheets[document.styleSheets.length-1];
        let str=select+"{";
        for(var prop in styles){
            str+=prop.replace(/[A-Z]/g,function(item){
                return "-"+item.toLocaleLowerCase();
            })+":"+styles[prop]+";";
        }
        str+="}"
        styleSheet.insertRule(str,styleSheet.cssRules.length);
    }
    static getIdElem(elem,obj){
        if(elem.id) obj[elem.id]=elem;
        if(elem.children.length===0) return obj;
        for(let i=0;i<elem.children.length;i++){
            Utils.getIdElem(elem.children[i],obj);
        }
    }
    static addClass(elem,className){
        let arr=(elem.className+" "+className).match(/\S+/g);
        arr=arr.filter((item,index)=>arr.indexOf(item,index+1)<0)
        elem.className=arr.join(" ");
    }
    static removeClass(elem,className){
        if(!elem.className) return;
        let arr=elem.className.match(/\S+/g);
        let arr1=className.match(/\S+/g);
        arr1.forEach(item=>{
            arr=arr.filter(t=>t!==item)
        })
        elem.className=arr.join(" ");
    }
    static hasClass(elem,className){
        if(!elem.className) return false;
        let arr=elem.className.match(/\S+/g);
        let arr1=className.match(/\S+/g);
        let res;
        arr1.forEach(item=>{
            res= arr.some(it=>it===item)
        })
        return res;
    }
    static loadImg({list,basePath,callback}){
        if(!list || list.length===0) return;
        if(basePath) list=list.map(item=>basePath+item);
        let img=Utils.createE("img");
        img.data={
            list:list,
            callback:callback,
            resultList:[],
            num:0
        }
        img.addEventListener("load",Utils.loadImgHandler);
        img.src=list[img.data.num];
    }
    static loadImgHandler(e){
        let data=e.currentTarget.data;
        data.resultList.push(e.currentTarget.cloneNode(false));
        data.num++;
        if(data.num>data.list.length-1){
            e.currentTarget.removeEventListener("load",Utils.loadImgHandler);
            data.callback(data.resultList);
            data=null;
            return;
        }
        e.currentTarget.src=data.list[data.num];
    }
}

版权声明:本文为Charissa2017原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。