3300.me

carousel

last update: 2021/05/17

sample

<!doctype html>
<html>
<head>
<title>title</title>
<meta name="viewport" content="width=560">

<style>
html, body {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  padding-bottom: 800px;
}
section {
  margin-bottom: 20px;
}
</style>
</head>

<body>

<section>
<div class="carousel1">
  <ul>
  <li style="background: #d22;">1</li>
  <li style="background: #2d2;">2</li>
  <li style="background: #22d;">3</li>
  </ul>
</div>
</section>

<section>
<div class="carousel2">
  <ul>
  <li style="background: #d22;">1</li>
  <li style="background: #2d2;">2</li>
  <li style="background: #22d;">3</li>
  </ul>
</div>
</section>

<section>
<div class="carousel3">
  <ul>
  <li style="background: #d22;">1</li>
  <li style="background: #2d2;">2</li>
  <li style="background: #22d;">3</li>
  <li style="background: #dd2;">4</li>
  <li style="background: #2dd;">5</li>
  </ul>
</div>
</section>

<section>
<div class="carousel4">
  <ul>
  <li style="background: #d22;">1</li>
  <li style="background: #2d2;">2</li>
  <li style="background: #22d;">3</li>
  <li style="background: #dd2;">4</li>
  <li style="background: #2dd;">5</li>
  </ul>
</div>
</section>




<script>
// carousel module
class carousel {
  constructor(option) {
    this.opt = option;
    this.d = document;
    this.ua = this.userAgent();
    this.init();
  }

  init() {
    this.opt.carousel = this.d.querySelector('.' + this.opt.class);

    if (!this.opt.carousel) {
      return;
  }

  Promise.resolve(1)
    // optionの値の一部をstyleとしてhtmlに適用
    .then(() => {
      this.setReadyStyles();
    })
    // 上記のstyleが適用されたdomから、pxに変換した値を取得
    .then(() => {
      this.setElementComputedSizes();
    })
    // optionから必要なhtmlを生成し、各種Listener、autoの制御、resize時の制御などを設定
    .then(() => {
      this.setConveyorHtml();
      this.setConveyorListeners();
      this.setPagenationHtml();
      this.setPagenationListeners();
      this.setArrowHtml();
      this.setArrowListeners();
      this.setAutoOperation();
      this.setBaseStyles();
      this.setResizeOperation();
    });
  }

  // 初回のstyle設定
  setReadyStyles() {
    // %やremにも対応する
    // 単位付きのstyleをまず設定する
    this.opt.orgWidth = this.opt.width;
    this.opt.orgHeight = this.opt.height;
    let styleStr = `
      .${this.opt.class} {
      position: relative;
      width: ${this.opt.width};
      height: ${this.opt.height};
      margin: 0 auto;
      }
      .${this.opt.class} ul, .${this.opt.class} li {
      margin: 0;
      padding: 0;
      }
      .${this.opt.class} li {
      list-style: none;
      }
      .${this.opt.class}_screen {
      width: ${this.opt.width};
      height: ${this.opt.height};
      position: relative;
      margin: 0 auto;
      overflow: hidden;
      }
      .${this.opt.class}_screen .${this.opt.class}_items {
      position: absolute;
      top: 0;
      width: ${this.opt.itemsWidth};
      -webkit-overflow-scrolling : touch;
      }
      .${this.opt.class}_screen .${this.opt.class}_items li {
      float: left;
      width: ${this.opt.width};
      height: ${this.opt.height};
      overflow: hidden;
      }
      .${this.opt.class}_screen .${this.opt.class}_items li img {
      width: ${this.opt.width};
      height: ${this.opt.height};
      }
    `;
    this.appendStyles(styleStr, this.opt.class + '_style');
  }

  // px数値を取得
  setElementComputedSizes() {
    this.opt.numWidth = this.getNumber(
      window.getComputedStyle(this.opt.carousel).width,
    );
    this.opt.numHeight = this.getNumber(
      window.getComputedStyle(this.opt.carousel).height,
    );
  }

  // 数値を取得
  getNumber(target) {
    return Number(target.match(/\d+/)[0]);
  }

  // pxに変換したstyleの設定
  setBaseStyles() {
    let referenceScreenSize;
    if (this.opt.numWidth < 400 && this.opt.numHeight < 400) {
      referenceScreenSize = 400;
    } else {
      referenceScreenSize =
      this.opt.numWidth > this.opt.numHeight ? this.opt.numWidth : this.opt.numHeight;
    }
    let styleStr = `
      .${this.opt.class} {
      position: relative;
      width: ${this.opt.numWidth}px;
      margin: 0 auto;
      }
      .${this.opt.class} ul, .${this.opt.class} li {
      margin: 0;
      padding: 0;
      }
      .${this.opt.class} li {
      list-style: none;
      }
      .${this.opt.class}_screen {
      width: ${this.opt.numWidth}px;
      height: ${this.opt.numHeight}px;
      position: relative;
      margin: 0 auto;
      overflow: hidden;
      }
      .${this.opt.class}_screen .${this.opt.class}_items {
      position: absolute;
      top: 0;
      width: ${this.opt.itemsWidth};
      -webkit-overflow-scrolling : touch;
      }
      .${this.opt.class}_screen .${this.opt.class}_items li {
      float: left;
      width: ${this.opt.numWidth}px;
      height: ${this.opt.numHeight}px;
      overflow: hidden;
      }
      .${this.opt.class}_screen .${this.opt.class}_items li img {
      width: ${this.opt.numWidth}px;
      height: ${this.opt.numHeight}px;
      }
    `;
    if (this.opt.pagenation) {
      const pagenationSize =
      referenceScreenSize / 50 > 16 ? 16 : referenceScreenSize / 50;
      const pagenationBorderRadius = pagenationSize;
      const pagenationMarginTop = referenceScreenSize / 50;
      styleStr += `
      .${this.opt.class}_pagenation {
        margin: ${pagenationMarginTop}px 0 0;
        text-align: center;
      }
      .${this.opt.class}_pagenation ul {
        display: inline-block;
      }
      .${this.opt.class}_pagenation li {
        float: left;
      }
      .${this.opt.class}_pagenation li + li {
        margin-left: ${pagenationSize}px;
      }
      `;
      styleStr += `
      .${this.opt.class}_pagenation li::before {
        content: "";
        display: block;
        width: ${pagenationSize}px;
        height: ${pagenationSize}px;
        background: #f1f1f1;
        border-radius: ${pagenationBorderRadius}px;
        cursor: pointer;
      }
      .${this.opt.class}_pagenation .active::before {
        background: #ccc;
        cursor: auto;
      }
      `;
    }
    if (this.opt.arrow) {
      const arrowSize = referenceScreenSize / 16;
      const arrowThickness = referenceScreenSize / 100;
      // const arrowTop = this.opt.numHeight / 2 - arrowSize / 2;
      styleStr += `
      .${this.opt.class}_arrow {
        width: ${this.opt.numWidth + arrowSize * 2.8}px;
        margin: 0 auto;
        position: absolute;
        top: ${this.opt.numHeight / 2.4}px;
        left: -${(this.opt.numWidth + arrowSize * 2.8 - this.opt.numWidth) / 2}px;
        text-align: center;
      }
      .${this.opt.class}_arrow div {
        display: block;
        width: ${arrowSize * 1.2}px;
        height: ${arrowSize * 1.7}px;
        position: relative;
        cursor: pointer;
      }
      .${this.opt.class}_arrow div::before {
        content: '';
        display: block;
        width: ${arrowSize}px;
        height: ${arrowSize}px;
        position: absolute;
        top: ${arrowSize / 3.5}px;
        border-top: #ccc ${arrowThickness}px solid;
        border-left: #ccc ${arrowThickness}px solid;
      }
      .${this.opt.class}_arrow .prev {
        float: left;
      }
      .${this.opt.class}_arrow .prev::before {
        transform: rotate(-45deg);
        left: ${arrowSize / 3}px;
      }
      .${this.opt.class}_arrow .next {
        float: right;
      }
      .${this.opt.class}_arrow .next::before {
        transform: rotate(135deg);
        right: ${arrowSize / 3}px;
      }
      `;
    }
    this.appendStyles(styleStr, this.opt.class + '_style');
  }

  // carousel dom の自動編集
  setConveyorHtml() {
    const ulCopy = this.d.querySelector('.' + this.opt.class + ' ul');
    const conveyorWrapper = this.d.createElement('div');

    conveyorWrapper.appendChild(ulCopy);
    this.opt.carousel.innerHTML = '';
    this.opt.carousel.appendChild(conveyorWrapper);
    this.opt.screen = this.opt.carousel.querySelector('div');
    this.opt.screen.classList.add(this.opt.class + '_screen');
    this.opt.carousel.querySelector('ul').classList.add(this.opt.class + '_items');
    this.opt.conveyor = this.d.querySelector(
      '.' + this.opt.class + '_screen .' + this.opt.class + '_items',
    );
    this.opt.items = this.d.querySelectorAll(
      '.' + this.opt.class + '_screen .' + this.opt.class + '_items li',
    );
    this.opt.itemsLength = this.opt.items.length;
    this.opt.itemsWidth = this.opt.numWidth * this.opt.itemsLength + 'px';

    // loop時は最初と最後にliを追加
    if (this.opt.loop) {
      const copyfirstItem = this.opt.items[0].cloneNode(true);
      const copyLastItem = this.opt.items[this.opt.itemsLength - 1].cloneNode(true);
      this.opt.conveyor.appendChild(copyfirstItem);
      this.opt.conveyor.insertBefore(copyLastItem, this.opt.conveyor.firstChild);
      this.opt.itemsLength += 2;
      this.opt.itemsWidth = this.opt.numWidth * this.opt.itemsLength + 'px';
      this.opt.conveyor.style.left = '-' + this.opt.numWidth + 'px';
    }
  }

  // dragなどのListener設定
  setConveyorListeners() {
    let flagStart;
    let flagMove;
    let startCursorX;
    let startConveyorX;
    let cursorMoveX;

    const getDirection = (ev) => {
      const pageX = ev.pageX || ev.changedTouches[0].pageX;
      cursorMoveX = startCursorX - pageX;
      if (Math.abs(cursorMoveX) < 50) {
        return true;
      }
    };

    // dragへの追従
    const dragSorption = (ev) => {
      if (this.opt.animation === 'fade') {
        return;
      }
      if (getDirection(ev)) {
        return;
      }
      const pageX = ev.pageX || ev.changedTouches[0].pageX;
      cursorMoveX = startCursorX - pageX;
      this.opt.conveyor.style.left = startConveyorX - cursorMoveX + 'px';
    };
    // touchstart時
    const startDrag01 = (ev) => {
      flagStart = true;
      startCursorX = ev.pageX || ev.changedTouches[0].pageX;
      startConveyorX = Number(this.opt.conveyor.style.left.replace('px', ''));
    };
    // touchmove時
    const startDrag02 = (ev) => {
      if (flagStart) {
      dragSorption(ev);
      flagMove = true;
      }
    };
    // touchend時にanimation発動
    const runAnimation = (ev) => {
      flagStart = false;
      const currentNumber = Number(
      -this.opt.conveyor.style.left.replace('px', ''),
      );
      const pageX = ev.pageX || ev.changedTouches[0].pageX;
      if (
      (flagMove && this.opt.animation === 'fade') ||
      currentNumber % this.opt.numWidth !== 0
      ) {
      this.setTargetBrakePoint(
        currentNumber,
        startCursorX - pageX < 0 ? 'prev' : 'next',
      );
      this.animation('-' + this.opt.targetPoint);
      flagMove = false;
      }
    };

    const handleTypes = this.ua.isPc
      ? ['mousedown', 'mousemove', 'mouseup']
      : ['touchstart', 'touchmove', 'touchend'];
    this.opt.conveyor.addEventListener(handleTypes[0], startDrag01, false);
    this.opt.conveyor.addEventListener(handleTypes[1], startDrag02, false);
    this.opt.conveyor.addEventListener(handleTypes[2], runAnimation, false);
    this.opt.conveyor.addEventListener(handleTypes[0], {
      passive: false,
    });
    this.opt.conveyor.removeEventListener(handleTypes[2], {
      passive: false,
    });

    // 指やカーソルがareaをはみ出た時にanimation発動
    this.opt.carousel.addEventListener('mouseout', runAnimation, false);
    }

    // pagenation dom 生成
    setPagenationHtml() {
    if (!this.opt.pagenation) {
      return;
    }
    // let basicStrings;
    let htmlStrings = this.templateLiteralToHtmlStrings`
      <div class="${this.opt.class}_pagenation">
      <ul>
    `;
    this.opt.pointsLength = this.opt.loop ? this.opt.itemsLength - 2 : this.opt.itemsLength;
    for (let i = 0; i < this.opt.pointsLength; i++) {
      htmlStrings += this.templateLiteralToHtmlStrings`
      <li></li>
      `;
    }
    htmlStrings += this.templateLiteralToHtmlStrings`
      </ul>
      </div>
    `;
    const htmlElements = this.htmlStringsToHtmlElements(htmlStrings);
    this.opt.carousel.appendChild(htmlElements);
    this.opt.pagenationItem = this.d.querySelectorAll(
      '.' + this.opt.class + '_pagenation li',
    );
    this.opt.pagenationItem[0].classList.add('active');
    }

    // pagenation押した時のListener設定
    setPagenationListeners() {
    if (!this.opt.pagenation) {
      return;
    }
    const eventType = this.getClickEventType();
    Object.values(this.opt.pagenationItem).forEach((item, index) => {
      const pos = this.opt.loop
      ? '-' + this.opt.numWidth * (index + 1)
      : '-' + this.opt.numWidth * index;
      const triggerAnimation = () => {
      this.animation(pos);
      };
      item.removeEventListener(eventType, triggerAnimation, false);
      item.addEventListener(eventType, triggerAnimation, false);
    });
  }

  // pagenationの表示変更
  changePagenation(targetPagenationIndex) {
    if (!this.opt.pagenation) {
      return;
    }
    Object.values(this.opt.pagenationItem).forEach((part) => {
      part.classList.remove('active');
    });
    if (this.opt.pagenationItem[targetPagenationIndex - 1]) {
      this.opt.pagenationItem[targetPagenationIndex - 1].classList.add('active');
    }
  }

  // arrowの設定
  setArrowHtml() {
    if (!this.opt.arrow) {
      return;
    }
    let htmlStrings = this.templateLiteralToHtmlStrings`
      <div class="${this.opt.class}_arrow">
      <div class="prev"></div>
      <div class="next"></div>
      </div>
    `;
    const htmlElements = this.htmlStringsToHtmlElements(htmlStrings);
    this.opt.carousel.appendChild(htmlElements);
    this.opt.arrow = this.d.querySelector('.' + this.opt.class + '_arrow');
    this.opt.arrow.prev = this.d.querySelector('.' + this.opt.class + '_arrow .prev');
    this.opt.arrow.next = this.d.querySelector('.' + this.opt.class + '_arrow .next');

    if (!this.opt.loop && this.getCurrentLeftNum() === 0) {
      this.opt.arrow.prev.style.opacity = '.2';
      this.opt.arrow.prev.style.cursor = 'auto';
    }
  }

  // arrow押した時のListener設定
  setArrowListeners() {
    if (!this.opt.arrow) {
      return;
    }
    const carouselReady = (diff) => {
      const currentNum = this.getCurrentLeftNum();
      if (currentNum % diff) {
      return;
      }
      if (currentNum === 0 && diff > 0) {
      return;
      }
      if (
      currentNum === -(this.opt.numWidth * (this.opt.itemsLength - 1)) &&
      diff < 0
      ) {
      return;
      }
      this.animation(currentNum + diff);
    };
    const eventType = this.getClickEventType();
    this.opt.arrow.prev.addEventListener(eventType, () => {
      carouselReady(this.opt.numWidth);
    });
    this.opt.arrow.next.addEventListener(eventType, () => {
      carouselReady(-this.opt.numWidth);
    });
  }

  // arrowのstyle切り替え
  changeArrow(targetItem) {
    if (!this.opt.arrow || this.opt.loop) {
      return;
    }
    if (targetItem === 0) {
      this.opt.arrow.prev.style.opacity = '.2';
      this.opt.arrow.prev.style.cursor = 'auto';
      this.opt.arrow.next.style.opacity = '1';
    } else if (targetItem === this.opt.itemsLength - 1) {
      this.opt.arrow.next.style.opacity = '.2';
      this.opt.arrow.next.style.cursor = 'auto';
      this.opt.arrow.prev.style.opacity = '1';
    } else {
      this.opt.arrow.prev.style.opacity = '1';
      this.opt.arrow.prev.style.cursor = 'pointer';
      this.opt.arrow.next.style.opacity = '1';
      this.opt.arrow.next.style.cursor = 'pointer';
    }
  }

  // arrowとpagenationの状態変更
  changeArrowAndPagenation(targetLeft) {
    if (!(this.opt.arrow || this.opt.pagenation)) {
      return;
    }
    this.opt.absTargetLeft = Math.abs(targetLeft);
    this.opt.targetIndex = this.opt.absTargetLeft / this.opt.numWidth;

    if (this.opt.loop) {
      if (this.opt.targetIndex === this.opt.itemsLength - 1) {
      this.changePagenation(1);
      } else if (this.opt.targetIndex === 0) {
      this.changePagenation(this.opt.itemsLength - 2);
      } else {
      this.changePagenation(this.opt.targetIndex);
      }
    } else {
      this.changeArrow(this.opt.absTargetLeft / this.opt.numWidth);
      this.changePagenation(this.opt.targetIndex + 1);
    }
  }

  // loopの場合のtarget調整
  adjustTarget(targetLeft) {
    if (!(this.opt.loop)) {
      return;
    }
    this.opt.absTargetLeft = Math.abs(targetLeft);
    this.opt.targetIndex = this.opt.absTargetLeft / this.opt.numWidth;

    window.setTimeout(() => {
      if (this.opt.targetIndex === this.opt.itemsLength - 1) {
        this.opt.conveyor.style.left = "-" + this.opt.numWidth + "px";
      } else if (this.opt.targetIndex === 0) {
        this.opt.conveyor.style.left =
          "-" + (this.opt.itemsLength - 2) * this.opt.numWidth + "px";
      }
    }, this.opt.duration * 1000);
  }

  // 各アニメーションに振り分ける
  animation(targetLeft) {
    switch (this.opt.animation) {
      case 'slide':
      this.slide(targetLeft);
      break;
      case 'fade':
      this.fade(targetLeft);
      break;
      default:
      break;
    }
  }

  // slideのanimation
  slide(targetLeft) {
    // ズレを直す
    if (
      /* eslint no-compare-neg-zero: 0 */
      targetLeft % this.opt.numWidth !== -0 ||
      Math.abs(targetLeft) > this.opt.numWidth * this.opt.itemsLength
    ) {
      targetLeft = 0;
    }
    // 現在の位置取得
    const currentLeft = this.getCurrentLeftNum();
    // keyframe準備
    const styleStr = `
      @keyframes ${this.opt.class}_slideCarousel {
      from {
        left: ${currentLeft}px;
      }
      to {
        left: ${targetLeft}px;
      }
      }
      .${this.opt.class}_items {
      animation-name: ${this.opt.class}_slideCarousel;
      animation-duration: ${this.opt.duration}s;
      animation-fill-mode: forwards;
      }
    `;
    // animation
    this.opt.conveyor.style.left = targetLeft + 'px';
    this.appendStyles(styleStr, this.opt.class + '_movingKeyframes');
    window.setTimeout(() => {
      const el = this.d.querySelector('.' + this.opt.class + '_movingKeyframes');
      if (el) {
      el.parentNode.removeChild(el);
      }
    }, this.opt.duration * 1000);

    this.changeArrowAndPagenation(targetLeft);
    this.adjustTarget(targetLeft);
  }

  // fade in/out のanimation
  fade(targetLeft) {
    // carouselをコピーしてずらして配置しそれぞれを fade in/out させる
    const copyDom = this.opt.conveyor.cloneNode(true);
    this.opt.conveyor.parentNode.insertBefore(copyDom, this.opt.conveyor);
    copyDom.style.left = targetLeft + 'px';
    copyDom.classList.add(this.opt.class + '_copyDom');

    const styleStr = `
      @keyframes ${this.opt.class}_fadeoutCarousel {
      from {
        opacity: 1;
      }
      to {
        opacity: 0;
      }
      }
      .${this.opt.class}_items {
      animation-name: ${this.opt.class}_fadeoutCarousel;
      animation-duration: ${this.opt.duration}s;
      animation-fill-mode: forwards;
      }
      @keyframes ${this.opt.class}_fadeinCarousel {
      from {
        opacity: 0;
      }
      to {
        opacity: 1;
      }
      }
      .${this.opt.class}_copyDom {
      animation-name: ${this.opt.class}_fadeinCarousel;
      animation-duration: ${this.opt.duration}s;
      animation-fill-mode: forwards;
      }
    `;

    this.appendStyles(styleStr, this.opt.class + '_movingKeyframes');
    window.setTimeout(() => {
      const el = this.d.querySelector('.' + this.opt.class + '_movingKeyframes');
      if (el) {
      el.parentNode.removeChild(el);
      }
    }, this.opt.duration * 1000);
    window.setTimeout(() => {
      this.opt.conveyor.style.left = targetLeft + 'px';
      copyDom.parentNode.removeChild(copyDom);
    }, this.opt.duration * 1000);

    this.changeArrowAndPagenation(targetLeft);
    this.adjustTarget(targetLeft);
  }

  // 現在のスライダの位置を取得
  getCurrentLeftNum() {
    return (
      this.opt.conveyor.getBoundingClientRect().left -
      this.opt.carousel.getBoundingClientRect().left
    );
  }

  // 次の目的位置設定
  setTargetBrakePoint(currentNumber, direction) {
    let targetIndex = Math.floor(currentNumber / this.opt.numWidth);
    let targetPoint = targetIndex * this.opt.numWidth;

    if (direction === 'next' && targetIndex !== this.opt.itemsLength - 1) {
      // 今最後で次のに行く時
      targetIndex = targetIndex + 1;
      targetPoint = targetPoint + this.opt.numWidth;
    } else if (currentNumber < 0) {
      // 今最初で前のに行く時
      targetIndex = 0;
      targetPoint = 0;
    } else if (direction === 'prev' && this.opt.animation === 'fade') {
      // fadeで前に行く時
      /* eslint no-compare-neg-zero: 0 */
      if (targetIndex === -0) {
      targetIndex = 0;
      targetPoint = 0;
      } else {
      targetIndex--;
      targetPoint -= this.opt.numWidth;
      }
    }

    this.opt.targetIndex = targetIndex;
    this.opt.targetPoint = targetPoint;
  }

  // deviceに適したclick event typeの設定
  getClickEventType() {
    return this.ua.isPc ? 'click' : 'touchstart';
  }

  // autoの設定
  setAutoOperation() {
    if (!this.opt.auto || !this.opt.auto.run) {
      return;
    }
    // 現実的でない数値だった時の調整
    if (this.opt.auto.time <= this.opt.duration * 1.5 || !this.opt.auto.time) {
      this.opt.auto.time = this.opt.duration * 10;
    }
    let id;
    // auto開始
    const startInterval = () => {
      id = setInterval(() => {
      const currentLeft = this.getCurrentLeftNum();
      this.animation(currentLeft - this.opt.numWidth);
      const lastItem =
        Math.abs(currentLeft / this.opt.numWidth) >= this.opt.itemsLength - 2;
      if (!this.opt.loop && lastItem) {
        clearInterval(id);
      }
      }, this.opt.auto.time * 1000);
    };
    // 一時停止
    const stopInterval = () => {
      clearInterval(id);
    };
    // 最後の一枚の時意図せず発動してしまうanimationを防ぐための準備段階
    const readyStartInterval = () => {
      const lastItem =
      Math.abs(this.getCurrentLeftNum() / this.opt.numWidth) >=
      this.opt.itemsLength - 1;
      if (!(!this.opt.loop && lastItem)) {
      startInterval();
      }
    };
    // カーソルや指がareaの上にある時の一時停止や離した時のリスタートListener設定
    if (this.opt.auto.stopMouseOver) {
      this.opt.conveyor.addEventListener('mouseover', stopInterval, false);
      this.opt.conveyor.addEventListener('mouseout', readyStartInterval, false);
      this.opt.conveyor.addEventListener('touchstart', stopInterval, false);
      this.opt.conveyor.addEventListener('touchend', readyStartInterval, false);
    }
    // カーソルがpagenationやarrowの上にある時の一時停止や離した時のリスタートListener設定
    // 指の場合はすぐ反応して欲しいため未設定
    const resetIntervalWhenClick = (target) => {
      Object.values(target).forEach((item) => {
      item.addEventListener('mousedown', stopInterval, false);
      item.addEventListener('mouseup', readyStartInterval, false);
      });
    };
    if (this.opt.arrow) {
      resetIntervalWhenClick(this.opt.arrow);
    }
    startInterval();
  }

  // resize時のstyle再設定
  // px以外の設定にしていたりする時に必要
  setResizeOperation() {
    if (/px/.test(this.opt.orgWidth)) {
      return;
    }
    window.addEventListener('resize', () => {
      const cleanStyles = () => {
      const currentStyles = this.d.querySelector('.' + this.opt.class + '_style');
      currentStyles.parentNode.removeChild(currentStyles);
      };

      Promise.resolve(1)
      .then(() => {
        cleanStyles();
        this.setReadyStyles();
      })
      .then(() => {
        this.setElementComputedSizes();
      })
      .then(() => {
        cleanStyles();
        this.setBaseStyles();
      })
      .then(() => {
        let currentNumber = Math.round(
        Number(this.opt.conveyor.style.left.match(/\d+/)) / this.opt.numWidth,
        );
        this.opt.conveyor.style.left = '-' + currentNumber * this.opt.numWidth + 'px';
        this.opt.conveyor.style.width = this.opt.itemsLength * this.opt.numWidth + 'px';
        this.setPagenationListeners();
      });
    });
  }

  // styleタグのappend関数
  appendStyles(styleStr, cls) {
    const styleDom = this.d.createElement('style');
    styleDom.classList.add(cls);
    styleDom.innerText = styleStr;
    const brs = styleDom.querySelectorAll('br');
    Object.values(brs).forEach((value) => {
      value.parentNode.removeChild(value);
    });
    this.opt.carousel.appendChild(styleDom);
    this.opt.style = this.opt.carousel.querySelector('.' + this.opt.class + '_style');
  }

  // utility
  templateLiteralToHtmlStrings(strings, ...values) {
    return strings.reduce((accumlate, str, i) => {
      return accumlate + this.escapeHtml(values[i - 1]) + str;
    });
  }
  htmlStringsToHtmlElements(htmlStrings) {
    const template = document.createElement('template');
    template.innerHTML = htmlStrings;
    return template.content.firstElementChild;
  }
  escapeHtml(str) {
    if (!str) {
      return '';
    }
    return str.replace(/["'`<>&]/g, function (m) {
      return {
      '"': '&quot;',
      "'": '&#x27;',
      '`': '&#x60;',
      '&lt;': '&lt;',
      '&gt;': '&gt;',
      '&amp;': '&amp;',
      }[m];
    });
  }
  userAgent() {
    const raw = window.navigator.userAgent;
    const ua = {};
    let deviceType;
    let osType;

    // sp
    if (/iPhone/.test(raw) || /iPod/.test(raw)) {
      deviceType = 'sp';
      osType = 'ios';
    } else if (/Android/.test(raw) && /Mobile/.test(raw)) {
      deviceType = 'sp';
      osType = 'android';
    } else if (/Windows/.test(raw) && /Phone/.test(raw)) {
      deviceType = 'sp';
    } else if (/Firefox/.test(raw) && /Mobile/.test(raw)) {
      deviceType = 'sp';
    } else if (/BlackBerry/.test(raw) || /BB10/.test(raw)) {
      deviceType = 'sp';
    } else if (/Nokia/.test(raw)) {
      deviceType = 'sp';

      // tablet
    } else if (/iPad/.test(raw)) {
      deviceType = 'tablet';
      osType = 'ios';
    } else if (/Macintosh/.test(raw) && 'ontouchend' in document) {
      deviceType = 'tablet';
      osType = 'ipados';
    } else if (/Android/.test(raw) && !/Mobile/.test(raw)) {
      deviceType = 'tablet';
      osType = 'android';
    } else if (
      /Windows/.test(raw) &&
      /Touch/.test(raw) &&
      !/Tablet PC/.test(raw)
    ) {
      deviceType = 'tablet';
    } else if (/Firefox/.test(raw) && /Tablet/.test(raw)) {
      deviceType = 'tablet';
    } else if (/Playbook/.test(raw)) {
      deviceType = 'tablet';
    } else if (/Kindle/.test(raw) || /Silk/.test(raw)) {
      deviceType = 'tablet';

      // pc
    } else {
      deviceType = 'pc';
      if (/Mac/.test(raw)) {
      osType = 'mac';
      } else if (/Windows/.test(raw)) {
      osType = 'win';
      } else {
      osType = 'unknown';
      }
    }

    ua.raw = raw;
    ua.deviceType = deviceType;
    ua.osType = osType;
    ua.isMs =
      deviceType === 'pc' &&
      /Windows NT/.test(raw) &&
      (!/Chrome/.test(raw) || /Firefox/.test(raw)); // ie or edge
    ua.isMac = osType === 'mac';
    ua.isWin = osType === 'win';
    ua.isIos = osType === 'ios';
    ua.isIpados = osType === 'ipados';
    ua.isAndroid = osType === 'android';
    ua.isPc = deviceType === 'pc';
    ua.isSp = deviceType === 'sp';
    ua.isTablet = deviceType === 'tablet';

    return ua;
  }
}


// instance生成とinit発火

new carousel({
  class: 'carousel1',
  width: '100%',
  height: '440px',
  animation: 'slide',
  duration: .4,
  pagenation: true,
  arrow: false,
  loop: true,
  auto: {
  run: false,
  time: 0,
  },
});

new carousel({
  class: 'carousel2',
  width: '220px',
  height: '220px',
  animation: 'fade',
  duration: .2,
  pagenation: true,
  arrow: true,
  loop: true,
  auto: {
  run: true,
  time: 2.0,
  },
});

new carousel({
  class: 'carousel3',
  width: '480px',
  height: '220px',
  animation: 'slide',
  duration: .2,
  pagenation: false,
  arrow: true,
  loop: false,
  auto: {
  run: false,
  time: 0,
  },
});

new carousel({
  class: 'carousel4',
  width: '400px',
  height: '800px',
  animation: 'slide',
  duration: .2,
  pagenation: true,
  arrow: true,
  loop: true,
  auto: {
  run: false,
  time: 0,
  },
});
</script>


</body>
</html>

code_popup