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

lanzhiheng · 2021年06月01日 · 最后由 lanzhiheng 回复于 2021年06月03日 · 766 次阅读

这篇文章主要简单讲讲在 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 的图片加载不出来。有没有办法把代理去掉?

lanzhiheng 回复

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

zhengpd 回复

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

lanzhiheng 回复

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

zhengpd 回复

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

需要 登录 后方可回复, 如果你还没有账号请 注册新账号