【轮播图的实现】JS的音乐播放器

轮播图使用的场景通常在网页首页上,在有限的空间可以通过轮播图,循环播放同一类型的图片、文字等内容。轮播图目前表现形式有 2 种,一种是常规的只出现一张图片,另一种是出现三张图片凸显一张的卡片化的。因为轮播图广泛使用,目前很多工具库(例如 swiper )都提供了现成的轮播图解决方案。但是作为一名合格的前端工程师不能只会使用现成的 api ,下面我来教你从零开始,从原理到代码一步步完成轮播图的制作。

知识点:
轮播图
事件防抖
通过上一篇博文,了解完 Ajax 和 Promise 的使用方法。接下来完成前端的接口开发。在 js 文件夹中新建 service 文件,同时在 service 文件夹下新建 ajax.js 文件(单独将前端请求接口提取到一个文件夹内,方便复用及调整)。在新建的文件中写入如下代码:

// ajax.js
const BASE_URL = "http://localhost:3000";
export default function Ajax({
  //请求参数配置
  method = "GET", //默认为'get'请求
  url,
  data = {},
}) {
  return new Promise((resolve) => {
    // 通过 Promise 返回异步请求
    const xhr = new XMLHttpRequest();
    xhr.open(method, BASE_URL + url);
    xhr.onload = function () {
      resolve(JSON.parse(xhr.response));
    };
    xhr.onerror = function () {
      // 待最后进行错误处理操作
      if (xhr.status == 0) {
      }
    };
    xhr.send(JSON.stringify(data));
  });
}

/**
 * @description: 获得轮播图信息
 * @param {*}
 * @return {*}
 */
export async function getBannerList() {
  const result = Ajax({
    url: `/homepage/block/page`,
  });
  return result;
}

通过 ESM 在 home.js 文件中引入 Ajax,完成前端数据接收。

//home.js
import { getBannerList } from "../service/ajax.js";

const result = await getBannerList();
const carouselData = result.data.blocks[0].extInfo.banners;

轮播图初始化
在进行轮播图样式初始化前,我们首先要了解下轮播图的原理。轮播图其实是将所有的图片排成一排(列),在显示区域仅显示一张,指定时间后显示区域出现下一张图片,同一图片每隔一段时间循环出现。

为了项目更容易管理,我们在 home 文件夹内新建一个 carousel.js 的文件来单独完成轮播图

//carousel.js
// 切换箭头为静态 HTML 样式,无需根据图片数量动态生成。
const carouselControl = `
<button class="carousel-control carousel-control-left carousel-control-hover">
<svg class="icon" aria-hidden="true">
    <use xlink:href="#icon-arrow-left"></use>
</svg>
</button>
<button class="carousel-control carousel-control-right carousel-control-hover">
<svg class="icon" aria-hidden="true">
    <use xlink:href="#icon-arrow-right"></use>
</svg>
</button>
`;
//轮播图配置
const carousel = {
  data: [], //轮播图数据
  currentIndex: 0, //轮播图当前切换的画面
  times: 2000, //轮播图多少时间切换画面
  animationTimes: 0.5, //轮播图动画持续时间,单位s
  autoCycleTimer: new Set(), //如果在切换动画,无法进行切换画面
};

export function carouselRender(data) {
  //初始化轮播图
  let carouselItem = "",
    carouselIndicatorsLi = "";
  const wrapper = document.querySelector(".carousel-wrapper");
  let { width = 0 } = wrapper.getBoundingClientRect(); //得到图片的宽度
  //动态生成轮播图
  data.forEach((item, index) => {
    //指示器激活选中判断
    let isActive = carousel.currentIndex == index ? "active" : "";
    //动态生成轮播图图片,并给每一张图片加上偏移量和动画效果
    carouselItem += `
            <div class="carousel-item ${
              "#" + index
            }" style='transform:translateX(${
      width * (index - 1)
    }px);transition-duration:${carousel.animationTimes}s'>
                <img src="${item.pic}" alt="">
            </div>
            `;
    //动态生成轮播图指示器
    carouselIndicatorsLi += `
                <li data-slide-to="${index}" class="carousel-indicators-li ${isActive}"></li>
            `;
  });
  // 通过模板字符串,按照 home.html 中的 html 结构进行排布
  const carouselContainer = `
        <div class="carousel-container" style="transition:transform ${carousel.animationTimes}s ">
            ${carouselControl}
            <div class="carousel-content">
                ${carouselItem}
            </div>
        </div>
        `;
  const carouselIndicators = `
        <ul class="carousel-indicators d-flex">
            ${carouselIndicatorsLi}
        </ul>
        `;
  // 将得到的字符串通过 innerHTML 插入到轮播图盒子
  wrapper.innerHTML = carouselContainer + carouselIndicators;
}
Ajax({
  url: "/homepage/block/page",
}).then((res) => {
  console.log(res);
  carousel.data = res.data.blocks[0].extInfo.banners;
  //首次渲染轮播图
  carouselRender(carousel.data);
});

注意在 home.css 文件中需要新增如下样式

/* home.css */
.carousel-container {
  position: relative;
  height: 300px;
  width: 100%;
  /* 新加的属性,调试过程中可先不加 */
  overflow: hidden;
}

/* 新加的属性 */
.carousel-content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.carousel-control {
  width: 25px;
  height: 25px;
  line-height: 25px;
  border-radius: 50%;
  border: none;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2021;
  background-color: rgba(255, 255, 255, 0.2);
  text-align: center;
  color: rgba(255, 255, 255, 0.6);
  /* 新增 */
  cursor: pointer;
}
.carousel-indicators > li {
  width: 50px;
  height: 4px;
  background-color: rgba(255, 255, 255, 0.4);
  margin: 15px 2px;
  /* 新增 */
  cursor: pointer;
}

1.自动轮播目前可以有以下两种方式,方案一:是在左右两侧均添加结尾和开始的图片,整体移动
在这里插入图片描述
该方法弊端在于需要新增图片,且在首尾切换的时候,会存在双倍的停留时间

方案二:每一张图片均进行移动,示意图如下:
在这里插入图片描述
此实验基于方案二来实现的,具体思路是通过改变数组的排序,数组的排序由当前显示区域图片的序号决定,开启定时器每隔一段事件序号+1 或者-1,既然知道思路了,下面我们就开始吧。

//carousel.js
function carouselRender(data) {
    //初始化轮播图,代码同轮播图初始化
    ...
    wrapper.innerHTML = carouselContainer + carouselIndicators;
    // 通过定时器开启自动轮播,每过一段时间调用 getNext 方法
    let timer = setInterval(getNext, carousel.times);
    carousel.autoCycleTimer.add(timer);
}
function getPrev() {
    // 获取到轮播图每一项的图片容器
    const carouselItems = document.getElementsByClassName('carousel-item');
    let length = carouselItems.length;
    // 当后退到第一张时,重置为总长度,防止index变为负数导致bug
    carousel.currentIndex == 0 && (carousel.currentIndex = length);
    // 每调用一次 getPrev,序号-1
    let index = carousel.currentIndex = --carousel.currentIndex % length;
    // 将类数组转变为数组
    let newArr = Array.from(carouselItems);
    // 计算得到轮播图每一项的图片容器的宽度
    let { width = 0 } = getElementRect(carouselItems[0]);
    // 轮播图数组移动
    newArr = [...newArr.slice(index), ...newArr.slice(0, index)];
    newArr.forEach((item, i) => {// 轮播图数组第一项移动到最后一项,其他项顺序不变
        if (i == 0) {
            item.style.transform = `translateX(${width * (length - 1)}px)`;
            item.style.opacity = 0;
        }
        item.style.transform = `translateX(${width * (i - 1)}px)`;
       item.style.opacity = 1;
    });
    // 指示器移动
    indicatorsRender(index);
}
function getNext() {
    const carouselItems = document.getElementsByClassName('carousel-item');
    let length = carouselItems.length;
    let index = carousel.currentIndex = ++carousel.currentIndex % length;

    let newArr = Array.from(carouselItems);
    let lens = newArr.length;
    let { width = 0 } = getElementRect(carouselItems[0]);
    //当index为0时轮播图数组不做处理,>0时进行数组每一项移动
    index != 0 && (newArr = [...newArr.slice(-index, lens), ...newArr.slice(0, lens - index)]);

    newArr.forEach((item, i) => {
        if (i == 0) {// 因为向右移动,轮播图数组最后一项移动到第一项,其他项顺序不变
            item.style.transform = `translateX(${-width * (length - 1)}px)`;
            item.style.opacity = 0;
        }
        item.style.transform = `translateX(${width * (i - 1)}px)`;
        item.style.opacity = 1;
    });

    indicatorsRender(index)
}
function indicatorsRender(index) {
    // 获取到轮播图每一项的指示器
    const indicators = document.getElementsByClassName('carousel-indicators-li');
    Array.from(indicators).forEach((item, i) => {
        if (index == i) { // 当 index 和指示器下标相同添加active类
            item.setAttribute('class', 'carousel-indicators-li active')
        } else {
            item.setAttribute('class', 'carousel-indicators-li')
        }
    })
}
function getElementRect(ele) {
    return ele.getBoundingClientRect();
}
Ajax({
    url: '/homepage/block/page'
}).then(res => {
    console.log(res);
    carousel.data = res.data.blocks[0].extInfo.banners;;
    //首次渲染轮播图
    carouselRender(carousel.data);
});

防抖
防抖就是特定时间内,防止重复操作执行多次事件处理函数。

防抖可以有以下应用场景:

登录、发请求等按钮避免用户点击太快,以致于发送了多次请求,需要防抖 — 立即执行
调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖 — 非立即执行
输入框内容校验时,等用户输入完成后再校验,需要用到防抖 — 非立即执行
因为防抖函数为公用函数,我们可以将其提出单独新建一个 js 文件,方便代码复用。 现在在 js 文件夹中新建 util 文件夹,新建 util.js 文件,导出 debounce 函数

// util.js
export function debounce(fn, times, isImmediately = true) {
  //防抖函数
  let timer = null;
  let cb;
  if (isImmediately) {
    cb = function (...args) {
      timer && clearTimeout(timer);
      let isDone = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, times);
      isDone && fn.apply(this, args);
    };
  } else {
    cb = function (...args) {
      const content = this;
      timer && clearTimeout(timer);
      timer = setTimeout(() => {
        fn.apply(content, args);
      }, times);
    };
  }
  return cb;
}

轮播图事件绑定
完成轮播图自动播放功能后,接下来还有三个功能需要添加:切换箭头实现图片上一张或者下一张切换;点击指示器跳转到指定图片;移入轮播图暂停轮播,移出轮播图重新轮播。

这三个功能都是通过事件来绑定的,我们可以将所有的事件处理放在同一个函数中,当轮播图初始化完成后集中触发

//carousel.js
import { debounce } from "../util/util.js";
function leftHandle() {
  //左切换箭头事件处理
  //清空定时器暂停轮播
  clearAllTimer();
  //切换到前一张
  getPrev();
  //开启定时器继续轮播,并将定时器加入到定时器保存器中
  let timer = setInterval(getNext, carousel.times);
  carousel.autoCycleTimer.add(timer);
}
function rightHandle() {
  //右切换箭头事件处理
  clearAllTimer();
  getNext();
  let timer = setInterval(getNext, carousel.times);
  carousel.autoCycleTimer.add(timer);
}
//函数防抖
const leftHandleDebounce = debounce(leftHandle, 500);
const rightHandleDebounce = debounce(rightHandle, 500);
export function initCarouselEvent() {
  const leftControl = document.getElementsByClassName("carousel-control-left");
  const rightControl = document.getElementsByClassName(
    "carousel-control-right"
  );
  const carouselContainer = document.querySelector(".carousel-container");
  const indicatorsWrapper = document.querySelector(".carousel-indicators");
  // 左右箭头切换事件
  leftControl[0].addEventListener("click", leftHandleDebounce);
  rightControl[0].addEventListener("click", rightHandleDebounce);

  // 移入移出控制轮播播放事件
  carouselContainer.addEventListener("mouseenter", () => {
    //移入轮播图通过移除定时器达到轮播图暂停的目的
    clearAllTimer();
  });

  carouselContainer.addEventListener("mouseleave", () => {
    //移出轮播图通过设置定时器达到开启轮播图轮播的目的
    let timer = setInterval(getNext, carousel.times);
    carousel.autoCycleTimer.add(timer);
  });
  //指示器事件处理函数:通过事件委托到父级容器 ul,减少对每个指示器添加事件监听
  indicatorsWrapper.addEventListener(
    "mouseenter",
    (e) => {
      if (e.target.tagName === "LI") {
        clearAllTimer();
        // 得到每个指示器的序号
        const index = e.target.getAttribute("data-slide-to");
        // 序号-1,调用getNext会+1,两者相抵消,根据序号指定到对应的图片
        carousel.currentIndex = index - 1;
        getNext();
        let timer = setInterval(getNext, carousel.times);
        carousel.autoCycleTimer.add(timer);
      }
    },
    true
  );
}
function clearAllTimer() {
  for (const i of carousel.autoCycleTimer) {
    clearInterval(i);
    if (carousel.autoCycleTimer > 100) {
      carousel.autoCycleTimer.clear();
    }
  }
}

轮播图作为一个首页常用功能,大部分的工具库都有现成的代码,所以在这块我们重要的是掌握原理和思想,要学会将一个项目拆分成很多小的功能点,一步步的实现。

目前项目大都是,前后端分离的。对于向后端数据请求,用的最多的是 axios,但 axios 是基于 Ajax 做的,对于 Ajax 我们还是得掌握和熟练使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

007的米奇妙妙屋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值