返回

解决多组幻灯片按钮失效问题:JS 完美修复

javascript

<h1>让你的多组幻灯片按钮动起来!</h1>

<h2>问题</h2>

<p>
  你是不是遇到过这样的情况:页面上有几组幻灯片,每组都有自己的“上一个”和“下一个”按钮,但是只有第一组的按钮能正常工作? 点击其他组的按钮,啥反应都没有,页面一动不动。 这让人很头疼!
</p>

<h2>原因分析</h2>

<p>
  问题很可能出在 JavaScript 代码里用来选择按钮的 CSS 选择器上。原来的代码可能只选中了第一组幻灯片的按钮,忽略了其他的。
</p>

<h2>解决方案</h2>

<p>
  想要搞定这个问题,咱们得让代码找到<em>所有</em>的按钮,并给它们都加上事件监听器。下面是几个可以尝试的方法:
</p>

<h3>方法一: 使用 querySelectorAll 和循环</h3>

<p>
  这是个比较直接的方法。咱们用 `querySelectorAll` 把所有符合条件的按钮都选出来,然后用一个循环,给每个按钮都加上点击事件的监听。
</p>

<p><strong>原理:</strong></p>
<ul>
    <li><code>querySelectorAll('.action-buttons .up-button')</code>: 这行代码找到页面上所有带有 "action-buttons" 类的元素,然后在这些元素里面寻找带有 "up-button" 类的子元素,也就是我们的向上按钮。</li>
  <li><code>querySelectorAll('.action-buttons .down-button')</code>: 这行代码做了类似的事情,只不过这次找的是向下按钮(带有 "down-button" 类的元素)。</li>
    <li><code>forEach(button =&gt; { ... })</code>:  对选取出来的所有按钮,逐一进行同样的处理。 <code>button</code>代表每一个被循环到的按钮.</li>
     <li><code>button.addEventListener('click', () =&gt; changeSlide('up'))</code> : 给按钮加个监听,一旦按钮被点("click" 事件发生),就运行  <code>changeSlide('up')</code> 函数。</li>
</ul>

<p><strong>代码示例:</strong></p>

```javascript
const sliderContainers = document.querySelectorAll('.slider-container');

sliderContainers.forEach(container => {
    const slideRight = container.querySelector('.right-slide');
    const slideLeft = container.querySelector('.left-slide');
    const upButton = container.querySelector('.up-button');
    const downButton = container.querySelector('.down-button');
    const slidesLength = slideRight.querySelectorAll('div').length;

    let activeSlideIndex = 0;

    slideLeft.style.top = `-${(slidesLength - 1) * 100}vh`;

    upButton.addEventListener('click', () => changeSlide(container, 'up', slidesLength));
    downButton.addEventListener('click', () => changeSlide(container, 'down', slidesLength));
});

function changeSlide(container, direction, slidesLength) {
    const sliderHeight = container.clientHeight;
    let activeSlideIndex = parseInt(container.dataset.activeSlideIndex || 0); //从container里面获取 index.

    if (direction === 'up') {
        activeSlideIndex++;
        if (activeSlideIndex > slidesLength - 1) {
            activeSlideIndex = 0;
        }
    } else {
        activeSlideIndex--;
        if (activeSlideIndex < 0) {
            activeSlideIndex = slidesLength - 1;
        }
    }

  const slideRight = container.querySelector('.right-slide');
  const slideLeft = container.querySelector('.left-slide');

    slideRight.style.transform = `translateY(-${sliderHeight * activeSlideIndex}px)`;
    slideLeft.style.transform = `translateY(${sliderHeight * activeSlideIndex}px)`;

  container.dataset.activeSlideIndex = activeSlideIndex; // 把最新的index存回去.
}

//以下代码无需修改.
window.kontext = function(container) {

  // Dispatched when the current layer changes
  var changed = new kontext.Signal();

  // All layers in this instance of kontext
  var layers = Array.prototype.slice.call(container.querySelectorAll('.layer'));

  // Flag if the browser is capable of handling our fancy transition
  var capable = 'WebkitPerspective' in document.body.style ||
    'MozPerspective' in document.body.style ||
    'msPerspective' in document.body.style ||
    'OPerspective' in document.body.style ||
    'perspective' in document.body.style;

  if (capable) {
    container.classList.add('capable');
  }

  // Create dimmer elements to fade out preceding slides
  layers.forEach(function(el, i) {
    if (!el.querySelector('.dimmer')) el.innerHTML += '<div class="dimmer"></div>';
  });

  /**
   * Transitions to and shows the target layer.
   *
   * @param target index of layer or layer DOM element
   */
  function show(target, direction) {

    // Make sure our listing of available layers is up to date
    layers = Array.prototype.slice.call(container.querySelectorAll('.layer'));

    // Flag to CSS that we're ready to animate transitions
    container.classList.add('animate');

    // Flag which direction
    direction = direction || (target > getIndex() ? 'right' : 'left');

    // Accept multiple types of targets
    if (typeof target === 'string') target = parseInt(target);
    if (typeof target !== 'number') target = getIndex(target);

    // Enforce index bounds
    target = Math.max(Math.min(target, layers.length), 0);

    // Only navigate if were able to locate the target
    if (layers[target] && !layers[target].classList.contains('show')) {

      layers.forEach(function(el, i) {
        el.classList.remove('left', 'right');
        el.classList.add(direction);
        if (el.classList.contains('show')) {
          el.classList.remove('show');
          el.classList.add('hide');
        } else {
          el.classList.remove('hide');
        }
      });

      layers[target].classList.add('show');

      changed.dispatch(layers[target], target);

    }

  }

  /**
   * Shows the previous layer.
   */
  function prev() {

    var index = getIndex() - 1;
    show(index >= 0 ? index : layers.length + index, 'left');

  }

  /**
   * Shows the next layer.
   */
  function next() {

    show((getIndex() + 1) % layers.length, 'right');

  }

  /**
   * Retrieves the index of the current slide.
   *
   * @param of [optional] layer DOM element which index is
   * to be returned
   */
  function getIndex(of) {

    var index = 0;

    layers.forEach(function(layer, i) {
      if ((of && of == layer) || (!of && layer.classList.contains('show'))) {
        index = i;
        return;
      }
    });

    return index;

  }

  /**
   * Retrieves the total number of layers.
   */
  function getTotal() {

    return layers.length;

  }

  // API
  return {

    show: show,
    prev: prev,
    next: next,

    getIndex: getIndex,
    getTotal: getTotal,

    changed: changed

  };

};

/**
 * Minimal utility for dispatching signals (events).
 */
kontext.Signal = function() {
  this.listeners = [];
}

kontext.Signal.prototype.add = function(callback) {
  this.listeners.push(callback);
}

kontext.Signal.prototype.remove = function(callback) {
  var i = this.listeners.indexOf(callback);

  if (i >= 0) this.listeners.splice(i, 1);
}

kontext.Signal.prototype.dispatch = function() {
  var args = Array.prototype.slice.call(arguments);
  this.listeners.forEach(function(f, i) {
    f.apply(null, args);
  });
}
// Create a new instance of kontext
var k = kontext(document.querySelector('.kontext'));

// Demo page JS

var bulletsContainer = document.body.querySelector('.bullets');

// Create one bullet per layer
for (var i = 0, len = k.getTotal(); i < len; i++) {
  var bullet = document.createElement('li');
  bullet.className = i === 0 ? 'active' : '';
  bullet.setAttribute('index', i);
  bullet.onclick = function(event) {
    k.show(event.target.getAttribute('index'))
  };
  bullet.ontouchstart = function(event) {
    k.show(event.target.getAttribute('index'))
  };
  bulletsContainer.appendChild(bullet);
}

// Update the bullets when the layer changes
k.changed.add(function(layer, index) {
  var bullets = document.body.querySelectorAll('.bullets li');
  for (var i = 0, len = bullets.length; i < len; i++) {
    bullets[i].className = i === index ? 'active' : '';
  }
});

document.addEventListener('keyup', function(event) {
  if (event.keyCode === 37) k.prev();
  if (event.keyCode === 39) k.next();
}, false);

进阶用法:

为了让每个slide的内容不一样, 可以把 slidesLength 等信息存放在 `slider-container`的`dataset`里面。

方法二:事件委托

事件委托是个更巧妙的方法。我们可以把事件监听器加在所有按钮的共同父元素上,然后通过事件对象 (event) 来判断是哪个按钮被点击了。

原理:

  • 事件会“冒泡”,也就是说,子元素上的事件会被传递到父元素。
  • 我们可以利用这一点,把事件监听器加在父元素上,减少监听器的数量。
  • 用 `event.target`找到实际被点击的目标.

代码示例 (这种做法在这个案例里需要对 HTML 结构有修改,所以先不展示):

由于你的 HTML 的按钮分别被各自的`slider-container`包裹, 使用事件委托比较麻烦, 反而不简洁。 方法一更好。

安全建议

  • 这段代码控制的是前端元素的行为, 没有直接和用户数据交互,所以没有直接的安全问题

这些方法都能解决你的问题!选一个你觉得最顺眼的就好。如果代码还是跑不通,或者你有别的问题,随时可以再问我!

```