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

in #cn7 years ago (edited)

最近剛好在實作捲軸下拉時,動態顯示出剩下來分頁的內容,所以參考了RailsCast Endless Page的影片,以下是實作後的筆記。

先做出簡單的頁面

假設我們需要在index page實作列出所有portfolio的效果

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

# 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>
# home_controller.rb
def index
  @portfolios = Portfolio.all
end

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

# home/index.html
<div id="products" class="row">
  <%= render @portfolios %>
</div>
# 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的內容。

實作分頁功能

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

首先我們需要使用kaminari這個gem,在Gemfile裡寫入

# Gemfile
gem 'kaminari'

執行bundle安裝gem

bundle install

接下來我們在home_controller中,加上以下的程式碼

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。

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

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

處理滾動捲軸的內容

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

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

卷軸滾動到特定位時觸發

首先先在home.coffee中加入以下的程式碼,我們會一步一步來解釋

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.coffeehome/index.js.erb兩個檔案中。

home.coffee程式碼

# 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,這樣我們就可以透過滾動到底部時取得下一頁的內容。

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

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

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

設置classj為paginate的element中的text。

  • $.getScipt(url)
    getScript其實是以下程式碼的簡寫
$.ajax({
  url: url,
  dataType: "script",
  success: success
});

目的是用來送出ajax 的GET request。所以我們可以知道getScript會對我們剛剛得到的/?page=2送出一個ajax的GET request。

index.js.erb

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

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

註:在*.js.erb這類的檔案中,我們可以混用ruby與js,然後編譯成js code後再傳回去給client端。

#  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?

如果你還記得的話,我們的home_controller中的index方法是

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取代第一頁。


這樣的話我們就可以一直不斷地抓取下一頁的內容。

  • <% sleep 1 %>

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

參考資料

RailsCast #114 Endless Page
Rails為何要使用escape_javascript?
Why escape_javascript before rendering a partial?
Layout, Render 與 View Helper

註:圖片取自Pixabay 創用CC並自行修改

Sort:  

@linjiahung, 这是小可可我在steemit最好的邂逅,好喜欢你的贴(^∀^)哇~~~ img