Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<div id="my-slider" class="slider-list"></div>
</body>
</html>
 
      /* 
      表现:CSS
      1、使用 CSS 绝对定位将图片重叠在同一位置
      2、轮播图切换的状态使用修饰符(modifier)
      3、轮播图的切换动画使用 CSS transition
       */
      #my-slider {
        position: relative;
        width: 800px;
        height: 300px;
      }
      .slider-list ul {
        list-style-type: none;
        position: relative;
        margin: 0;
        padding: 0;
      }
      .slider-list__item,
      .slider-list__item--selected {
        position: absolute;
        transition: opacity 1s;
        opacity: 0;
        text-align: center;
      }
      .slider-list__item--selected {
        transition: opacity 1s;
        opacity: 1;
      }
      /* 轮播图交互元素 */
      .slide-list__previous,
      .slide-list__next {
        position: absolute;
        top: 50%;
        margin-top: -25px;
        display: inline-block;
        width: 30px;
        height: 50px;
        text-align: center;
        font-size: 24px;
        line-height: 50px;
        overflow: hidden;
        border: none;
        background: transparent;
        color: white;
        background: rgba(0, 0, 0, 0.2);
        cursor: pointer;
        opacity: 0;
        transition: opacity 0.5s;
      }
      .slide-list__previous {
        left: 0;
      }
      .slide-list__previous::after {
        content: "<";
      }
      .slide-list__next {
        right: 0;
      }
      .slide-list__next::after {
        content: ">";
      }
      #my-slider:hover .slide-list__previous,
      #my-slider:hover .slide-list__next {
        opacity: 1;
      }
      .slide-list__control {
        position: relative;
        display: table;
        margin: auto;
        background-color: rgba(255, 255, 255, 0.5);
        padding: 3px;
        border-radius: 12px;
        top:300px;        
        
        /* 
        不知道什么原因
        设置 bottom:30px;
        slide-list__control 向上溢出 slider-list
         */
      }
      .slide-list__control-buttons,
      .slide-list__control-buttons--selected {
        display: inline-block;
        width: 15px;
        height: 15px;
        border-radius: 50%;
        margin: 0 5px;
        background-color: white;
        cursor: pointer;
      }
      .slide-list__control-buttons--selected {
        background-color: red;
      }
 
      /*
      行为:JS
      1. API 设计应保证原子操作,职责单一,满足灵活性。
      2. Slider
        2.1. getSelectedItem()
        2.2. getSelectedItemIndex()
        2.3. slideTo()
        2.4. slideNext()
        2.5. slidePrevious()
       */
      class Component {
        constructor(id, opts = { name, data: [] }) {
          this.container = document.getElementById(id);
          this.options = opts;
          this.container.innerHTML = this.render(opts.data);
        }
        // 注册插件
        registerPlugins(...plugins) {
          plugins.forEach(plugin => {
            const pluginContainer = document.createElement("div");
            pluginContainer.className = `.${name}__plugin`;
            pluginContainer.innerHTML = plugin.render(this.options.data);
            this.container.appendChild(pluginContainer);
            plugin.action(this);
          });
        }
        render(data) {
          /* abstract */
          return "";
        }
      }
      class Slider extends Component {
        constructor(id, opts = { name: "slider-list", data: [], cycle: 3000 }) {
          super(id, opts);
          /*
          this.items 指向一个 non-live 的 nodeList (由元素节点组成的 array like object)
          该 nodeList 是一个静态对象,并不会随着所选元素的变化而变化
           */
          this.items = this.container.querySelectorAll(
            ".slider-list__item, .slider-list__item--selected"
          );
          // 轮播间隔
          this.cycle = opts.cycle || 3000;
          // 类实例化时,轮播图默认跳转至第一项
          this.slideTo(0);
        }
        /*
        根据 opts.images 数据
        渲染轮播图页面结构
         */
        render(data) {
          const content = data.map(image =>
            `
            <li class="slider-list__item">
              <img src="${image}" />
            </li>
          `.trim()
          );
          /*
          content.join('')
          数组转字符串
          并将分隔符 , 替换为 ''
          */
          return `<ul>${content.join("")}</ul>`;
        }
        getSelectedItem() {
          const selected = this.container.querySelector(
            ".slider-list__item--selected"
          );
          return selected;
        }
        getSelectedItemIndex() {
          /*
          Array.from()
          可将 arrayLike(类似数组或可迭代对象)创建一个新的,浅拷贝的数组实例
           */
          return Array.from(this.items).indexOf(this.getSelectedItem());
        }
        slideTo(idx) {
          const selected = this.getSelectedItem();
          if (selected) {
            selected.className = "slider-list__item";
          }
          /*
          NodeList.item()
          返回 NodeList 对象中指定索引的节点,如果索引越界,则返回 null。
          等价写法为:nodeList[index],这种情况下,越界访问将返回 undefined。
           */
          const item = this.items[idx];
          if (item) {
            item.className = "slider-list__item--selected";
          }
          /*
          this.container
          添加自定义事件
          实现 slide-list__control 与 slide-list 解耦
           */
          const detail = { index: idx };
          const slideEvent = new CustomEvent("slide", {
            bubbles: true,
            detail,
          });
          this.container.dispatchEvent(slideEvent);
        }
        slideNext() {
          const currentIdx = this.getSelectedItemIndex();
          /*
          考虑到 nodeList 最后一项索引 + 1,
          与 nodeList 长度取余,
          获取 nodeList 第一项索引
           */
          const nextIdx = (currentIdx + 1) % this.items.length;
          this.slideTo(nextIdx);
        }
        slidePrevious() {
          const currentIdx = this.getSelectedItemIndex();
          /*
          考虑到 nodeList 第一项索引 - 1
          加上 nodeList 长度,再与其取余
          获取 nodeList 最后一项索引
           */
          const previousIdx =
            (this.items.length + currentIdx - 1) & this.items.length;
          this.slideTo(previousIdx);
        }
        start() {
          this.stop();
          this._timer = setInterval(() => this.slideNext(), this.cycle);
        }
        stop() {
          clearInterval(this._timer);
        }
      }
      // 交互逻辑对象
      const pluginController = {
        // HTML 结构渲染方法
        render(images) {
          return `
            <div class="slide-list__control">
              ${images
                .map(
                  (image, index) =>
                    `
                  <span
                    class="slide-list__control-buttons${
                      index === 0 ? "--selected" : ""
                    }">
                  </span>
                `
                )
                .join("")}
            </div>
          `;
        },
        // 交互事件方法
        action(slider) {
          const controller = slider.container.querySelector(
            ".slide-list__control"
          );
          if (controller) {
            const buttons = controller.querySelectorAll(
              ".slide-list__control-buttons, .slide-list__control-buttons--selected"
            );
            controller.addEventListener("mouseover", evt => {
              const idx = Array.from(buttons).indexOf(evt.target);
              if (idx >= 0) {
                slider.slideTo(idx);
                slider.stop();
              }
            });
            controller.addEventListener("mouseout", evt => {
              slider.start();
            });
            slider.container.addEventListener("slide", evt => {
              const idx = evt.detail.index;
              const selected = controller.querySelector(
                ".slide-list__control-buttons--selected"
              );
              if (selected) {
                selected.className = "slide-list__control-buttons";
              }
              buttons[idx].className = "slide-list__control-buttons--selected";
            });
          }
        },
      };
      const pluginPrevious = {
        render() {
          return `<a class="slide-list__previous"></a>`;
        },
        action(slider) {
          const previous = slider.container.querySelector(
            ".slide-list__previous"
          );
          if (previous) {
            previous.addEventListener("click", evt => {
              slider.stop();
              slider.slidePrevious();
              slider.start();
              evt.preventDefault();
            });
          }
        },
      };
      const pluginNext = {
        render() {
          return `<a class="slide-list__next"></a>`;
        },
        action(slider) {
          const next = slider.container.querySelector(".slide-list__next");
          if (next) {
            next.addEventListener("click", evt => {
              slider.stop();
              slider.slideNext();
              slider.start();
              evt.preventDefault();
            });
          }
        },
      };
      const slider = new Slider("my-slider", {
        name: "slider-list",
        data: [
          'https://p5.ssl.qhimg.com/t0119c74624763dd070.png',
          'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg',
          'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg',
          'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'
        ],
        cycle: 3000,
      });
      // 注册插件
      slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
      slider.start();
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
hua-qipro
0viewers