原生js封裝輪播圖

工作原因,好久沒更新博客了,今天來個用原生js寫輪播圖, 上代碼

class Banner {
   
   
  constructor(props = {
   
   }) {
   
   
    if (!(props?.root instanceof HTMLElement)) {
   
   
      throw "root是必選dom節點";
    }
    // 狀態
    this.state = {
   
   };
    // 參數
    this.props = {
   
   
      type: "move",
      height: 600,
      width: 1000,
      imgs: [],
      ...props,
    };
    if (props.type === "move") {
   
   
      this.imgIndex = props.startIndex ? props.startIndex + 1 : 1;
    } else {
   
   
      this.imgIndex = props.startIndex || 0;
    }
    this.autoTimer; //自動輪播計時器
    this.animateTimer; //動畫計時器
    this.init();
  }

  // 修改狀態
  setState = (state = {
   
   }) => {
   
   
    this.state = {
   
   
      ...this.state,
      ...state,
    };
  };

  // 初始化
  init = () => {
   
   
    this.render();
    this.animate();
    this.autoMove();
    this.onChange();
  };
  // 渲染輪播圖
  render = () => {
   
   
    const {
   
   
      root,
      height,
      width,
      type,
      imgs = [],
      showButton = true,
      showCheck = true,
    } = this.props;
    setStyle(root, {
   
   
      height,
      width,
      position: "relative",
    });
    root.className += " banner";
    const box = document.createElement("div");
    const imgBox = document.createElement("ul");
    const iconBox = document.createElement("ul");

    imgs.forEach((item) => {
   
   
      this.setImgNode(imgBox, item);
      const icon = document.createElement("li");
      iconBox.appendChild(icon);
    });
    // 滾動需要前後各插入圖片
    if (type === "move") {
   
   
      this.setImgNode(imgBox, imgs[0]);
      this.setImgNode(imgBox, imgBox.children[0], imgs[imgs.length - 1]);
    } else {
   
   
      setStyle(imgBox.children[this.imgIndex], {
   
   
        zIndex: 90,
        opacity: 1,
      });
    }

    setStyle(imgBox, {
   
   
      width: type === "move" ? (imgs.length + 2) * width : width,
      height,
    });
    addClass(
      box,
      type === "move" ? "banner-img-box-move" : "banner-img-box-transparency"
    );
    iconBox.className = "banner-icon-box";

    box.appendChild(imgBox);
    root.appendChild(box);
    showCheck && root.appendChild(iconBox);
    showButton && this.renderButton(root);
    this.setState({
   
   
      box,
      imgBox,
      iconBox,
    });
  };

  // 渲染按鈕
  renderButton = (node) => {
   
   
    const rightBtn = document.createElement("button");
    const leftBtn = document.createElement("button");
    rightBtn.innerHTML = ">";
    leftBtn.innerHTML = "<";
    rightBtn.className = "btn banner-right-btn";
    leftBtn.className = "btn banner-left-btn";

    node.appendChild(rightBtn);
    node.appendChild(leftBtn);
    this.setState({
   
   
      rightBtn,
      leftBtn,
    });
  };

  // 設置圖片函數
  setImgNode(node) {
   
   
    const {
   
    height, width, type } = this.props;
    const li = document.createElement("li");
    li.innerHTML = `<img class="banner-img" src="${
     
     
      arguments.length > 2 ? arguments[2] : arguments[1]
    }" />`;
    setStyle(li, {
   
   
      height,
      width,
      opacity: type === "move" ? null : 0,
    });
    node.insertBefore(li, arguments.length > 2 ? arguments[1] || null : null);
  }

  //自動輪播
  autoMove = () => {
   
   
    const {
   
    time = 3000 } = this.props;
    this.autoTimer = setInterval(() => {
   
   
      this.imgIndex++;
      this.animate();
    }, time);
  };

  //動畫
  animate = () => {
   
   
    const {
   
    type } = this.props;
    const {
   
    iconBox } = this.state;
    //dom集合轉數組
    Array.from(iconBox.children).forEach((node, index) => {
   
   
      if (node.className?.indexOf("active") > -1) {
   
   
        removeClassName(node, "active");
      }
      if (this.imgIndex === (type === "move" ? index + 1 : index)) {
   
   
        addClass(node, "active");
      }
    });
    if (type === "move") {
   
   
      this.move();
    } else {
   
   
      this.transparency();
    }
  };
  //滾動輪播
  move = () => {
   
   
    const {
   
    iconBox, box } = this.state;
    const {
   
    imgs, width } = this.props;

    // 下標等於圖片長度時需要重置到第一個小圓點
    if (this.imgIndex === imgs.length + 1) {
   
   
      addClass(iconBox.children[0], "active");
    }
    // 圖片超過長度拉回第二張圖片
    if (this.imgIndex === imgs.length + 2) {
   
   
      this.imgIndex = 2;
      box.scrollLeft = width;
      addClass(iconBox.children[1], "active");
    }

    if (this.imgIndex === 0) {
   
   
      addClass(iconBox.children[imgs.length - 1], "active");
    }

    //下標小於0時返回最後二張圖片的下標
    if (this.imgIndex < 0) {
   
   
      this.imgIndex = imgs.length - 1;
      box.scrollLeft = width * imgs.length;
      addClass(iconBox.children[imgs.length - 2], "active");
    }
    //滾動條移動
    this.scrollMove();
  };

  //淡入淡出
  transparency = () => {
   
   
    const {
   
    imgBox, iconBox } = this.state;
    const {
   
    imgs } = this.props;
    if (this.imgIndex === imgs.length) {
   
   
      //下標到最後一張圖片時變回第一張圖片

      this.imgIndex = 0;
      addClass(iconBox.children[0], "active");
    }
    if (this.imgIndex < 0) {
   
   
      
      this.imgIndex = imgs.length - 1;
      addClass(iconBox.children[imgs.length - 1], "active");
    }
    Array.from(imgBox.children).forEach((item, index) => {
   
   
      this.opacitySwitch(item, 0);
      item.style.zIndex = 0;
    })
    this.opacitySwitch(imgBox.children[this.imgIndex], 100);
    imgBox.children[this.imgIndex].style.zIndex = 90;
  };

  // 所有操作函數
  onChange = () => {
   
   
    const {
   
    iconBox } = this.state;
    const {
   
    root, type } = this.props;
    iconBox.onmouseover = (e) => {
   
   
      if (e.target.nodeName !== "LI") return;
      clearInterval(this.autoTimer);
      const index = [].indexOf.call(e.target.parentNode.children, e.target);
      this.imgIndex = type === "move" ? index + 1 : index;
      this.animate();
      this.autoMove(); //重新啓動
    };
    root.onclick = (e) => {
   
   
      if (e.target.className?.indexOf("banner-right-btn") > -1) {
   
   
        clearInterval(this.autoTimer);
        this.imgIndex++;
        this.animate();
        this.autoMove();
      } else if (e.target.className?.indexOf("banner-left-btn") > -1) {
   
   
        clearInterval(this.autoTimer);
        this.imgIndex--;
        this.animate();
        this.autoMove();
      }
    };
  };

  //透明度切換
  opacitySwitch = (ele, target) => {
   
   
    let num = 10;
    clearInterval(this.animateTimer);
    this.animateTimer = setInterval(() => {
   
   

      let speed = target > num ? 5 : -5;
      //剩餘可運動量 <= 每次所走的量
      if (Math.abs(target - num) <= Math.abs(speed)) {
   
   
        clearInterval(this.animateTimer); //結束運動
        ele.style.opacity = target / 100; //到達終點
      } else {
   
   
        num += speed;
        ele.style.opacity = num / 100;
      }
    }, 30);
  };

  //滾動條移動
  scrollMove() {
   
   
    const {
   
    box, imgBox } = this.state;
    const {
   
    width } = this.props;
    clearInterval(this.animateTimer); //清除計算器
    let minStep = 0; //起始步數
    let maxStep = 20; //最大步數
    let start = box.scrollLeft; //運動起始位置
    let end = this.imgIndex * width; //結束位置
    let everyStep = (end - start) / maxStep; //每一步的距離
    this.animateTimer = setInterval(() => {
   
   
      minStep++;
      if (minStep >= maxStep) {
   
   
        //判斷到達最大步數
        clearInterval(this.animateTimer); //清除計算器
      }
      start += everyStep; //起始位置加上走的距離
      box.scrollLeft = start;
    }, 20);
  }
}

// 設置css
function setStyle(root, style = {
   
   }) {
   
   
  Object.keys(style).forEach(
    (item) => (root.style[item] = getStyleValue(item, style[item]))
  );
}
// 特殊處理設置寬高樣式 數字
function getStyleValue(key = "", value) {
   
   
  const arr = ["height", "width", "top", "left", "right", "bottom", "border"];
  return arr.indexOf(key.toLowerCase()) > -1 &&
    value !== 0 &&
    value &&
    !isNaN(value)
    ? value + "px"
    : value;
}
// 增加類名
function addClass(ele, value) {
   
   
  if (!ele.className) {
   
   
    ele.className = value;
  } else {
   
   
    newClassName = ele.className;
    newClassName += " ";
    newClassName += value;
    ele.className = newClassName;
  }
}

// 刪除class函數
function removeClassName(ele, className) {
   
   
  let str = ele.className,
    index = str.indexOf(className);
  if (index > -1) {
   
   
    ele.className = str.replace(className, "");
  }
}

css 部分寫的稍微簡陋一些

* {
   
   
  margin: 0;
  padding: 0;
}
li {
   
   
  list-style: none;
}
.banner {
   
   
  position: relative;
}
.banner-img-box-move {
   
   
  overflow: hidden;
  height: 100%;
  width: 100%;
  position: relative;
}
.banner-img-box-move > ul > li > img {
   
   
  height: 100%;
  width: 100%;
}
.banner-img-box-move > ul > li {
   
   
  float: left;
}
.banner-icon-box {
   
   
  position: absolute;
  right: 20px;
  bottom: 20px;
  z-index: 99;
  display: flex;
}
.banner-icon-box > li {
   
   
  height: 15px;
  width: 15px;
  margin-right: 12px;
  background-color: #eee;
  border-radius: 50%;
}
.banner-img-box-transparency {
   
   
  position: relative;
}
.banner-img-box-transparency > ul > li {
   
   
  position: absolute;
  left: 0;
  top: 0;
}
.banner-img-box-transparency> ul > li >img{
   
   
  height: 100%;
  width: 100%;
}
.btn {
   
   
  position: absolute;
  height: 40px;
  width: 40px;
  z-index: 99;
}
.banner-left-btn {
   
   
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}
.banner-right-btn {
   
   
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}
.active {
   
   
  background-color: red !important;
}

使用


<script>

const imgs = ['images/1.jpg','images/2.jpg','images/3.jpg','images/4.jpg','images/5.jpg',]

const root = document.querySelector('#root');
new Banner({
    
    
    imgs,
    root,
    type: 'transparency', //move是滾動輪播, transparency是透明度
    width: 1000,
    height: 400,
})
</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章