Rails Hotwire 环境下搭配使用传统轮播组件 Slick

lanzhiheng · June 01, 2021 · Last by lanzhiheng replied at June 03, 2021 · 738 hits

这篇文章主要简单讲讲在 Rails 项目中如何在 Hotwire 环境下使用传统轮播图组件 Slick。原文链接 https://www.lanzhiheng.com/posts/slick-on-hotwire (由于 RubyChina 暂时无法浏览 gif 图,要看完整的效果动画还请移步到原文)。

前言

针对没有前后端分离的项目,我们一般会借用类似Slick这样的组件来做轮播图(快两年没人维护了,真的没事吗?)。对于 N 年前的 FullPageReload 项目来说,我们根本不用关心 Slick 相关组件销毁的问题。然而对于引入了Turbo的项目,事情可能要稍微复杂一点点了。接下来笔者会简单剖析一下相关的问题,并提供思路与解决方案。

传统配合方式的演变

在传统的开发模式下,针对页面的 JavaScript 代码一般都是这幅德行

$(document).ready(function() {
  // ....
})

如果是使用了 Turbo 的 Rails 项目,那可以是这个样子

$(document).on('turbo:load', function() {
  // ...
})

所以搭配 Turbo 使用,一个常规的 Slick 轮播图代码大概是这个样子的

.one-time {
  width: 300px;
  margin-top: 300px;
  border: 1px solid black;
  margin: 300px auto;

  h3 {
    text-align: center;
  }
}
<div class="one-time">
  <div><h3>1</h3></div>
  <div><h3>2</h3></div>
  <div><h3>3</h3></div>
  <div><h3>4</h3></div>
  <div><h3>5</h3></div>
  <div><h3>6</h3></div>
</div>
$(document).on('turbo:load', function() {
  $('.one-time').slick({
    dots: true,
    infinite: true,
    speed: 300,
    slidesToShow: 1,
    adaptiveHeight: true
  });
})

令人怀念的前端三剑客。效果如下

first-slick-image.png

咋一看貌似一劳永逸,然而....点击页面上任何的链接离开当前页面,然后再点击浏览器的返回按钮试试?点击浏览器的返回按钮理论上就会触发了 Turbo 的Restoration Visits。这个时候你会发现 1). 原来轮播图无法点击(失效了)。2. 控制台 JavaScript 脚本报错。

screen.gif

接下来就着重解决一下,通过浏览器来返回或者前进的时候所造成的 Slick 轮播组件失效的问题。

页面切换之前先销毁

根据StackOverflow的说法,之所以会出现这种问题,是因为 Slick 对应组件多次被初始化了。

因为 Turbo 的 Restoration Visits 会从缓存中拿历史页面,而在历史页面中对应的组件其实已经是初始化后的 Slick 组件了。对 Slick 组件,再次进行初始化就会引发前面提到的异常。建议就是如果目标组件没有经过 Slick 初始化,才运行对应的初始化代码所以

$(document).on('turbo:load', function() {
  $('.one-time').not('.slick-initialized').slick({
    dots: true,
    infinite: true,
    speed: 300,
    slidesToShow: 1,
    adaptiveHeight: true
  });
})

妙啊,这个时候点击返回的时候错误信息已经没有了。然而返回的历史页面中轮播图不可用这个问题依旧还是存在。对于有生命周期的组件来说这个问题比较简单,等到生命周期的最后注销这个组件,需要用的时候再重新初始化就好了。然而在 Turbo 的页面跳转中没有生命周期这个概念,那怎么办呢?可以这样做

$(document).on('turbo:visit', function() {
  $('.one-time').filter('.slick-initialized').slick('unslick');
})

就是每次要跳转页面 (turbo:visit) 的时候先把对应的组件销毁,然而在资源加载后 (turbo:load) 重新进行初始化。这样一来哪怕使用的是浏览器的返回或者前进按钮进行页面浏览,也不会出现轮播图不可使用的问题了。

with-flash.gif

只是这种做法在进行常规页面跳转的时候,会因为 Slick 组件的销毁导致页面闪烁。如果你的轮播图不是在首屏,那问题不大,然而如果刚好在首屏,那体验是相当的不好,接下来我们再利用 Hotwire 里面的黑科技来进一步解决这个问题。

借助 Stimulus 提供的生命周期

上面的做法虽然勉强能用,但会有一些交互上的体验问题,而且这样的案例一多,势必会在同一个事件中注册太多的回调动作,维护性将越来越差。这种时候我们就会怀念 React,Vue 提供的生命周期,以组件为模块管理代码,并且等生命周期的最后进行组件销毁

其实 Hotwire 也提供了这样的东西,就是Stimulus,我们来看看它是否可行。

<div class="two-time" data-controller="two-time">
  <div><h3>1</h3></div>
  <div><h3>2</h3></div>
  <div><h3>3</h3></div>
  <div><h3>4</h3></div>
  <div><h3>5</h3></div>
  <div><h3>6</h3></div>
</div>

样式就跟之前的一样就好

.one-time,
.two-time {
  width: 300px;
  margin-top: 300px;
  border: 1px solid black;
  margin: 300px auto;

  h3 {
    text-align: center;
  }
}
// app/packs/controllers/two_time_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  initialize() {
    $(this.element).slick({
      dots: true,
      infinite: true,
      speed: 300,
      slidesToShow: 1,
      adaptiveHeight: true
    });
  }

  disconnect() {
    $(this.element).slick('unslick');
  }
}

效果

final.gif

这样一来体验更好了,前面的one-time例子出现的闪烁问题也不复存在了。而且代码是以组件为模块来组织的,相对来说更好维护一些。

尾声

这篇文章主要简单总结一下在 Hotwire 的环境下如何搭配使用“老旧”的轮播组件 Slick。提供了两种方案

  1. 传统的 JQuery 模式的写法,利用 Turbo 的事件回调来完成组件的初始化和销毁,这种方式可维护性差,而且页面闪烁较为严重。
  2. 利用了 Hotwire 中的 Stimulus 框架所提供的生命周期概念,在加载完成之后进行组件的初始化,生命周期最后进行组件的销毁,这种做法交互体验更好,维护性也高一些。

希望以后能挖掘出更好的做法。

@huacnlee 貌似 GIF 的图片加载不出来。有没有办法把代理去掉?

Reply to lanzhiheng

感觉是 bug,提 pr 的机会来了 🤔

Reply to zhengpd

不一定吧,可能是代理配置问题?能找 @Rei 帮忙看看不?

Reply to lanzhiheng

看了下 homeland 只是做 image src 的替换,imageproxy 是支持 gif 的,所以估计是 imageproxy 配置问题

Reply to zhengpd

好像可以了。可能他们改了配置。

You need to Sign in before reply, if you don't have an account, please Sign up first.