[Rails]實作捲軸產生無限捲軸功能 by linjiahung

View this thread on steempeak.com
· @linjiahung · (edited)
$0.02
[Rails]實作捲軸產生無限捲軸功能
![](https://i.imgur.com/r279VHK.png)

最近剛好在實作捲軸下拉時,動態顯示出剩下來分頁的內容,所以參考了[RailsCast Endless Page](http://railscasts.com/episodes/114-endless-page-revised)的影片,以下是實作後的筆記。

## 先做出簡單的頁面
假設我們需要在index page實作列出所有portfolio的效果

![](https://i.imgur.com/NLScYTd.png)

我們最一開始的view和controller應該會長這樣

```ruby=
# home/index.html.erb
<div id="products" class="row">
  <% @portfolios.each do |portfolio| %>
    <div class="col-lg-2 col-sm-6 portfolio-item product">
      <div class="card h-100">
        <a href="#">
          <%= image_tag portfolio.image.url, class: "card-img-top"%>
        </a>
      </div>
    </div>
  <% end %>
</div>
```

```ruby=
# home_controller.rb
def index
  @portfolios = Portfolio.all
end
```

為了等下的實作方便,讓我們來改寫一下目前的頁面,我們要把程式碼移到partial中,然後用render來渲染。

```ruby=
# home/index.html
<div id="products" class="row">
  <%= render @portfolios %>
</div>
```

```ruby=
# portfolios/_potfolio.html.erb
<div class="col-lg-2 col-sm-6 portfolio-item product">
  <div class="card h-100">
    <a href="#">
      <%= image_tag portfolio.image.url, class: "card-img-top"%>
    </a>
  </div>
</div>
```

> 註:render collection的用法,可以參考龍哥的這一篇[Layout, Render 與 View Helper](https://railsbook.tw/chapters/15-layout-render-and-view-helper.html)的內容。

## 實作分頁功能
接下來我們要實作分頁功能,因為我們的無限捲軸功能也是透過javascript去產生原本頁面上的分頁來完成的。

首先我們需要使用`kaminari`這個gem,在Gemfile裡寫入
```ruby=
# Gemfile
gem 'kaminari'
```
執行bundle安裝gem
```
bundle install
```

接下來我們在`home_controller`中,加上以下的程式碼
```ruby=
def index
  @portfolios = Portfolio.all.page(params[:page]).per(6)
end
```
讓我們看一下我們都新增了哪些東西
- page(params[:page])

這段程式碼可以params判別目前的頁數是第幾頁。比如說當我們點擊第四頁的時候,我們就可以透過`params[:page]`得到目前的頁數。
```
http://localhost:3000/?page=4
```
- per(6)

這代表你每幾個objects一頁。

接下來在view中加入`<%= paginate @portfolios %>`,這段程式碼可以幫我們產生分頁所需的link。

```ruby=
<div id="products" class="row">
  <%= render @portfolios %>
</div>
<%= paginate @portfolios %>
```

![](https://i.imgur.com/TVzVche.png)

到這邊我們就已經產生了分頁的效果。

## 處理滾動捲軸的內容
接下來我們要實作的內容是,當向下滾動到某個區域的時候,網頁會自動顯示接下來的分頁。

- 卷軸滾動到特定位置時觸發
- 即時渲染下一個分頁的內容

### 卷軸滾動到特定位時觸發
首先先在`home.coffee`中加入以下的程式碼,我們會一步一步來解釋

```javascript=
jQuery ->
  $(window).scroll ->
    if $(window).scrollTop() > $(document).height() - $(window).height() - 60
      alert "Near Bottom"
```
- $(window).scroll()

代表當我們滾動視窗時,就會觸發內部的程式碼
- $(window).scrollTop()

`scrollTop()`可以讓我們設定該元素(這裡是window)跟頂部的位移。當你尚未開始滾動時,位移是零。
- $(window).height() 和 $(document).height()

這可以讓我們得到document跟目前視窗的高度

所以這段程式碼的邏輯就是,當頁面向下滾動時,如果頁面的偏移量,大於整個document的高度 - 視窗的高度 - 60時,觸發alert。

### 即時渲染下一個分頁的內容
接下來讓我們改寫並加入新的程式碼到`home.coffee`和`home/index.js.erb`兩個檔案中。

#### home.coffee程式碼
```ruby=
# home.coffee
jQuery ->
  $(window).scroll ->
    url = $('.pagination .next a').attr('href')
    if url && $(window).scrollTop() > $(document).height() - $(window).height() - 60
      $('.pagination').text("載入更多圖片")
      $.getScript(url)
```
- url = $('.pagination .next a').attr('href')

這段程式碼會得到頁面中,Next的anchor的href值。這段程式碼的重要之處在於,我們接下來可以透過url還有query string,幫我們取得「下一頁」應該要渲染出的object,這樣我們就可以透過滾動到底部時取得下一頁的內容。

我們可以看一下以下的截圖。

![](https://i.imgur.com/qsbrhLS.png)

而我們的url就會是`/?page=2`,這邊對應的path就是`root_path`,並加上一段query string(`page=2`)。

- $('.pagination').text("載入更多圖片")

設置classj為paginate的element中的text。

![](https://i.imgur.com/jhBf5iT.png)

- $.getScipt(url)
getScript其實是以下程式碼的簡寫
```javascript=
$.ajax({
  url: url,
  dataType: "script",
  success: success
});
```
目的是用來送出ajax 的GET request。所以我們可以知道getScript會對我們剛剛得到的`/?page=2`送出一個ajax的GET request。

### index.js.erb
接續剛剛上一段的`$.getScipt(url)`,其實就是對url送出get請求。

由於我們的`root_path`(`root 'home#index'`)對應到的是`home_controller`下的index method,所以我們如果可以在`app/view/home`的資料夾下,新增一個`index.js.erb`檔來處理request format為js的情況。

> 註:在*.js.erb這類的檔案中,我們可以混用ruby與js,然後編譯成js code後再傳回去給client端。
```ruby=
#  home/index.js.erb
$('#products').append('<%= j render(@portfolios) %>');
<% if @portfolios.next_page %>
  $('.pagination').replaceWith('<%= j paginate(@portfolios) %>');
<% else %>
  $('.pagination').remove();
<% end %>
<% sleep 1 %>
```

- $('#products').append('<%= j render(@portfolios) %>');

這段程式碼會在id為products的element後方,插入`render(@portfolios)`渲染出的html。

這邊另外比較值得注意的是`j()`的用法。

`j()`其實是`escape_javascript()`的縮寫,目的是跳脫雙引號帶來的束縛,不會因為在內部出現多個雙引號導致javascript壞掉。更多內容可以參考這篇[Rails為何要使用escape_javascript?](http://noelsaga.herokuapp.com/blog/2015/09/25/railswei-he-yao-shi-yong-escape-javascript/)。

如果你還記得的話,我們的`home_controller`中的index方法是
```ruby=
def index
  @portfolios = Portfolio.all.page(params[:page]).per(6)
end
```
當我們對home/index送出get request時,我們還額外送出了一個query string`?page=2`。所以我們等於是透過送出ajax request還有query string,得到了「下一頁(第二頁)」中的object。

- <% if @portfolios.next_page %>
  $('.pagination').replaceWith('<%= j paginate(@portfolios) %>');
<% else %>
  $('.pagination').remove();
<% end %>

這段程式碼代表的意思是,當目前的頁面的下一頁還有object時,將目前的分頁link用下一頁的分頁link取代掉。也就是假設目前是第一頁,當我們往下滾到定點時,用第二頁的分頁link取代第一頁。
![](https://i.imgur.com/uEOUccL.png)
![](https://i.imgur.com/5oSbwdJ.png)
這樣的話我們就可以一直不斷地抓取下一頁的內容。

- <% sleep 1 %>

讓程式暫停1秒再繼續執行。這邊可以自行決定要不要使用,如果不希望分頁滾動時渲染太快,可以考慮使用。

## 參考資料
[RailsCast #114 Endless Page](http://railscasts.com/episodes/114-endless-page)
[Rails為何要使用escape_javascript?](http://noelsaga.herokuapp.com/blog/2015/09/25/railswei-he-yao-shi-yong-escape-javascript/)
[Why escape_javascript before rendering a partial?](https://stackoverflow.com/questions/1620113/why-escape-javascript-before-rendering-a-partial)
[Layout, Render 與 View Helper](https://railsbook.tw/chapters/15-layout-render-and-view-helper.html)

註:圖片取自[Pixabay 創用CC](https://pixabay.com/en/scroll-parchment-document-pergament-152864/)並自行修改
👍  , ,
properties (23)
post_id50,655,804
authorlinjiahung
permlinkrails
categorycn
json_metadata"{"image": ["https://i.imgur.com/r279VHK.png"], "links": ["http://railscasts.com/episodes/114-endless-page-revised", "https://railsbook.tw/chapters/15-layout-render-and-view-helper.html", "http://noelsaga.herokuapp.com/blog/2015/09/25/railswei-he-yao-shi-yong-escape-javascript/", "http://railscasts.com/episodes/114-endless-page", "https://stackoverflow.com/questions/1620113/why-escape-javascript-before-rendering-a-partial", "https://pixabay.com/en/scroll-parchment-document-pergament-152864/"], "format": "markdown", "tags": ["cn", "cn-reader", "ruby", "rails"], "users": ["portfolios", "portfolios.next"], "app": "steemit/0.1"}"
created2018-05-28 13:18:12
last_update2018-05-29 07:07:51
depth0
children1
net_rshares5,647,153,553
last_payout2018-06-04 13:18:12
cashout_time1969-12-31 23:59:59
total_payout_value0.019 SBD
curator_payout_value0.002 SBD
pending_payout_value0.000 SBD
promoted0.000 SBD
body_length5,552
author_reputation52,749,970,637
root_title[Rails]實作捲軸產生無限捲軸功能
beneficiaries[]
max_accepted_payout1,000,000.000 SBD
percent_steem_dollars10,000
author_curate_reward""
vote details (3)
@cn-cutie.pie ·
@linjiahung, 这是小可可我在steemit最好的邂逅,好喜欢你的贴(^∀^)哇~~~ ![img](https://i.imgur.com/cd4haG7.png)
👍  ,
properties (23)
post_id50,657,894
authorcn-cutie.pie
permlink20180528t133606697z-post
categorycn
json_metadata"{"tags": ["cn"]}"
created2018-05-28 13:36:06
last_update2018-05-28 13:36:06
depth1
children0
net_rshares4,574,885,974
last_payout2018-06-04 13:36:06
cashout_time1969-12-31 23:59:59
total_payout_value0.000 SBD
curator_payout_value0.000 SBD
pending_payout_value0.000 SBD
promoted0.000 SBD
body_length88
author_reputation717,060,097,040
root_title[Rails]實作捲軸產生無限捲軸功能
beneficiaries[]
max_accepted_payout1,000,000.000 SBD
percent_steem_dollars10,000
author_curate_reward""
vote details (2)