触控友好滑块背后的 JavaScript

Avatar of Kevin Foley
Kevin Foley

DigitalOcean 为您旅程的每个阶段提供云产品。从 $200 免费积分 开始!

以下是 Kevin Foley 的客座文章。Kevin 是 Squarespace 的开发者,除了其他事情外,他还利用 开发者平台 做着很酷的事情。他最近一直在做一个可滑动的图片库,他同意在这里分享他的一些工作成果!

几周前,Chris 发布了一个创建 带有滑动背景图片的滑块 的教程。几乎在同一时间,我正在做一些新的可滑动的画廊,所以 Chris 建议我写一篇关于如何在滑块中添加滑动支持的教程。它就在这里!

创建可滑动的画廊时,有两个技术(我知道的)可以选择。您可以动画化滚动位置,或者使用 `translate` 移动元素。两者各有优缺点。

使用 Translate

用 `translate` 移动滑块可以让你获得硬件加速和亚像素动画的优势。但是,在最初的触控事件中,你可能会注意到一个小的延迟(只有几十毫秒),然后滑块才会开始移动。这一点没有得到很好的记录,我只是在我的经验中注意到了它。

使用 Overflow Scroll

Overflow Scroll 对初始触控非常敏感,因为它属于浏览器的原生功能。你不需要等待 JavaScript 中的事件监听器。但是你失去了使用 translate 移动元素的所有平滑度。

在本教程中,我们将使用 `translate`,因为我认为它看起来更好。

HTML

本例中的 HTML 将与 Chris 的原始示例有所不同。我们不会将图片设置为背景图片,而是将其设置为一个元素。这样就可以移动图片,从而获得使用 translate 而不是动画化背景位置的酷炫平移效果。

<div class="slider-wrap">
  <div class="slider" id="slider">
    <div class="holder">
      <div class="slide-wrapper">
        <div class="slide"><img class="slide-image" src="//farm8.staticflickr.com/7347/8731666710_34d07e709e_z.jpg" /></div>
        74
      </div>
      <div class="slide-wrapper">
        <div class="slide"><img class="slide-image" src="//farm8.staticflickr.com/7384/8730654121_05bca33388_z.jpg" /></div>
        64
      </div>
      <div class="slide-wrapper">
        <div class="slide"><img class="slide-image" src="//farm8.staticflickr.com/7382/8732044638_9337082fc6_z.jpg" /></div>
        82
      </div>
    </div>
  </div>
</div>

CSS

在很大程度上,CSS 与 Chris 的 CSS 相同,因此我不会重复如何设置布局。但有一些关键的差异。

我们不仅要添加 overflow scroll,还要对幻灯片进行动画化。因此,我们将使用一个类来设置过渡,并在需要时用 JavaScript 添加它。

.animate { transition: transform 0.3s ease-out; }

IE 10 处理触控事件的方式与 Chrome 和 Safari 等移动 Webkit 浏览器不同。我们将通过 JavaScript 处理 Webkit 触控事件,但在 IE 10 中,我们可以只用 CSS 创建整个滑块(几乎)。

.ms-touch.slider {
  overflow-x: scroll;
  overflow-y: hidden;
  
  -ms-overflow-style: none;
  /* Hides the scrollbar. */
  
  -ms-scroll-chaining: none;
  /* Prevents Metro from swiping to the next tab or app. */
  
  -ms-scroll-snap-type: mandatory;
  /* Forces a snap scroll behavior on your images. */
  
  -ms-scroll-snap-points-x: snapInterval(0%, 100%);
  /* Defines the y and x intervals to snap to when scrolling. */
}

由于这些属性对大多数人来说可能很陌生(对我来说也是如此),因此我将逐一讲解每个属性及其功能。

-ms-scroll-chaining

Surface 平板电脑在您在页面上滑动时会切换浏览器标签页,这使得所有滑动事件对开发者来说毫无用处。幸运的是,通过将任何给定元素的 scroll chaining 设置为 none,可以很容易地覆盖这种行为。

-ms-scroll-snap-type

当设置为 mandatory 时,此属性会覆盖浏览器的默认滚动行为,并强制可滚动元素捕捉到某个间隔。

-ms-scroll-snap-points-x

此属性设置可滚动元素捕捉到的间隔。它接受两个数字:第一个数字是起始点;第二个数字是捕捉间隔。在本例中,每个幻灯片都是父元素的整个宽度,这意味着间隔应该是 100%(即元素会捕捉到 100%、200%、300% 等)。

-ms-overflow-style

此属性允许您在将其设置为 none 时隐藏滚动条。

JavaScript

我们在 JavaScript 中要做的第一件事是检测我们正在使用哪种触控设备。IE 10 使用指针事件,而 Webkit 则使用“touchstart”、“touchmove”和“touchend”。由于 IE 10 滑块(几乎)完全用 CSS 实现,因此我们需要检测它并向包装器添加一个类。

if (navigator.msMaxTouchPoints) {
  $('#slider').addClass('ms-touch');
}

很简单。如果您现在测试滑块,它将是一个可用的可滑动幻灯片。但是我们还需要在图片上添加平移效果。

if (navigator.msMaxTouchPoints) {
  $('#slider').addClass('ms-touch');

  // Listed for the scroll event and move the image with translate.
  $('#slider').on('scroll', function() {
    $('.slide-image').css('transform','translate3d(-' + (100-$(this).scrollLeft()/6) + 'px,0,0)');
  });
}

这就是 IE 10 的全部内容。

现在来看看 Webkit 的方法。所有这些都将包装在 `else` 语句中。首先,我们只需要定义几个变量。

else {
  var slider = {

    // The elements.
    el: {
      slider: $("#slider"),
      holder: $(".holder"),
      imgSlide: $(".slide-image")
    },

    // The stuff that makes the slider work.
    slideWidth: $('#slider').width(), // Calculate the slider width.

    // Define these as global variables so we can use them across the entire script.
    touchstartx: undefined,
    touchmovex: undefined, 
    movex: undefined,
    index: 0,
    longTouch: undefined,
    // etc

然后我们需要初始化函数并定义事件。

    // continued

    init: function() {
      this.bindUIEvents();
    },

    bindUIEvents: function() {

      this.el.holder.on("touchstart", function(event) {
        slider.start(event);
      });

      this.el.holder.on("touchmove", function(event) {
        slider.move(event);
      });

      this.el.holder.on("touchend", function(event) {
        slider.end(event);
      });

    },

现在来看看真正能让东西在您滑动滑块时发生变化的有趣部分。

Touchstart

在 iPhone(以及大多数其他触控滑块)上,如果您缓慢移动滑块,只有一点点,它会弹回原来的位置。但如果您快速移动,它会递增到下一张幻灯片。这种快速移动称为弹动。没有原生方法来测试弹动,所以我们必须使用一个小技巧。在 touchstart 上,我们初始化一个 setTimeout 函数,并在一定时间后设置一个变量。

this.longTouch = false;
setTimeout(function() {
  // Since the root of setTimout is window we can’t reference this. That’s why this variable says window.slider in front of it.
  window.slider.longTouch = true;
}, 250);

我们还需要获取触控的原始位置,以便我们的动画起作用。如果您以前从未做过,它有点奇怪。JavaScript 允许您定义多点触控事件,因此您可以传递触控事件一个表示您要监听的手指数量的数字。在本例中,我实际上只关心一个手指/拇指,因此在下面的代码示例中,这就是 [0] 的作用。

// Get the original touch position.
this.touchstartx =  event.originalEvent.touches[0].pageX;

在我们开始移动滑块之前,我将删除 animate 类。我知道现在元素上没有 animate 类,但我们需要它用于后续的幻灯片。我们将在 touchend 上将它重新添加到元素中。

$('.animate').removeClass('animate');

Touchmove

touchmove 事件的行为类似于 JavaScript 中的 scroll 事件。也就是说,如果您在 scroll 上做了一些事情,那么在 scroll 发生时它会执行很多次。因此,我们将不断获取 touchmove 位置,直到手指/拇指移动。

// Continuously return touch position.
this.touchmovex =  event.originalEvent.touches[0].pageX;

然后,我们将使用我们在上一次事件中获得的 touchstart 位置和 touchmove 位置进行快速计算,以确定如何平移幻灯片。

// Calculate distance to translate holder.
this.movex = this.index*this.slideWidth + (this.touchstartx - this.touchmovex);

然后,我们需要平移图片,就像 Chris 在原始示例中所做的那样。我们将使用他使用的相同神奇数字。

// Defines the speed the images should move at.
var panx = 100-this.movex/6;

现在,我们需要添加一些逻辑来处理边缘情况。如果您在第一张幻灯片或最后一张幻灯片上,如果您向错误的方向滚动(即没有内容的方向),则此逻辑将停止图片平移。这可能不是处理这种情况的最佳方法,但它目前对我有用。

if (this.movex < 600) { // Makes the holder stop moving when there is no more content.
  this.el.holder.css('transform','translate3d(-' + this.movex + 'px,0,0)');
}
if (panx < 100) { // Corrects an edge-case problem where the background image moves without the container moving.
  this.el.imgSlide.css('transform','translate3d(-' + panx + 'px,0,0)');
 }

Touchend

在 touchend 事件中,我们需要弄清楚用户移动滑块的距离、速度以及此操作是否应该递增到下一张幻灯片。

首先,我们需要确定滑动的确切距离。我们将计算移动距离的绝对值,以查看用户是否进行了滑动操作。

// Calculate the distance swiped.
var absMove = Math.abs(this.index*this.slideWidth - this.movex);

现在,我们将确定滑块是否应该递增。本例中所有其他计算都基于索引变量,因此这是脚本背后的真正逻辑。它检查用户是否滑动了足够距离以递增滑块,或者移动是否是轻弹。如果满足其中任一条件,则滑动方向如何。

// Calculate the index. All other calculations are based on the index.
if (absMove > this.slideWidth/2 || this.longTouch === false) {
  if (this.movex > this.index*this.slideWidth && this.index < 2) {
    this.index++;
  } else if (this.movex < this.index*this.slideWidth && this.index > 0) {
    this.index--;
  }
}

现在,我们添加动画类并设置新的平移位置。

// Move and animate the elements.
this.el.holder.addClass('animate').css('transform', 'translate3d(-' + this.index*this.slideWidth + 'px,0,0)');
this.el.imgSlide.addClass('animate').css('transform', 'translate3d(-' + 100-this.index*50 + 'px,0,0)');

结论

所以,是的,为 IE 10 欢呼吧。

但说真的,考虑到这是移动操作系统上的一个常见习语,创建可滑动的画廊有点麻烦。最终结果不会像原生滑动那样好。但会很接近。

完整的演示在这里

Check out this Pen!

但是你最好在你的触控设备上查看 这里