Karn Tirasoontorn

July 25, 2021

ปล่อยให้ข้อมูลค่อยๆ โหลดเมื่อถึงเวลา (Lazy Loading)

การแสดงผลข้อมูลประเภทกราฟต่างๆ ในหน้า dashboard ถ้ามีจำนวนไม่กี่กราฟ Query ที่ใช้ก็คงมีไม่กี่ชุด การรอดึงข้อมูลให้ครบก่อนแล้วจึงจะนำไปแสดงผลอาจจะไม่ทำให้ UX ของผู้ใช้งานรู้สึกแย่สักเท่าไหร่ เพราะคงใช้เวลาไม่กี่วินาที แต่เมื่อไหร่ก็ตามที่จำนวนกราฟมากขึ้น เงื่อนไขที่ใช้มากขึ้น Query ที่ใช้ก็ย่อมมีหลายชุดตามไปด้วย ทั้งนี้ย่อมส่งผลให้เวลาใช้ก็มากขึ้นตาม ถ้าจะรอให้ดึงข้อมูลครบทั้งหมด ผู้ใช้งานคงรอเป็นนาทีๆ เลยทีเดียว


จากตัวอย่างข้างต้น ผู้ใช้งานต้องการดึงข้อมูลหลายเดือน มีการเลือกที่จะแสดงตัวชี้วัดหลายตัวทีเดียว และผลตอบรับที่ได้กลับมาคือผู้ใช้งานต้องใช้เวลารอนานมากๆๆๆๆๆ เพราะปัจจุบันการดึงข้อมูลตรงนี้จะรอให้ดึงข้อมูลทุกๆ กราฟให้เสร็จเรียบร้อยก่อน และส่งผลกลับมาแสดงบนให้ผู้ใช้งานได้เห็นหรือที่เรียกว่า Eager Loading

จุดนี้เองทำให้เราจะต้องปรับเปลี่ยนการแสดงข้อมูลเป็น Lazy Loading แทนซึ่งน่าจะช่วยให้ UX ของผู้ใช้งานดีขึ้น ในบทความนี้เราจะมาลอง POC กันก่อนโดยมีแนวคิดที่ว่าทันทีที่ส่วนของการแสดงกราฟปรากฏขึ้นบน viewport จึงจะค่อยไปดึงข้อมูล และนำกราฟมาแสดง ซึ่งเราจะใช้ Turbo และ Stimulus 

  • สร้าง controller เพื่อใช้ตรวจสอบว่าส่วนแสดงผลปรากฏขึ้นมาแล้วหรือยังด้วย Intersection Observer และทันทีที่ปรากฏก็สั่งให้ Turbo Frame ทำการโหลดข้อมูลจาก src ที่ได้กำหนดไว้

# hyper_controller.js
import { Controller } from "stimulus"
# AppearanceObserver from https://github.com/hotwired/turbo-rails/blob/ba86832f7f13001793ab917185788df9723666e8/app/assets/javascripts/turbo.js#L436
import { AppearanceObserver } from "../helpers"

export default class extends Controller {  
  static values = { src: String }
  initialize () {    
    this.connected = false    
    this.appearanceObserver = new AppearanceObserver(this, this.element)  
  }
  
  connect () {    
    if (!this.connected) { 
      this.connected = true      
      this.appearanceObserver.start()    
    }  
  }    

  disconnect () {    
    if (this.connected) {      
      this.appearanceObserver.stop()    
    }  
  }
  
  elementAppearedInViewport(element) {    
    this.loadSourceURL()  
  }

  loadSourceURL() {    
    this.element.src = this.srcValue       
    this.appearanceObserver.stop()  
  }
}

  • จำลองส่วนการแสดงข้อมูลเอาไว้ และกำหนด controller ข้างต้นเข้าไปใน Turbo Frame

# demo.html.erb
  
<% 10.times.each do |index| %>    
  <div class="py-10">      
    <%= turbo_frame_tag "graph_#{index}", data: { controller: "hyper", "hyper-src-value": snippet_path(id: index) } do %>        
      <div class="border border-blue-300 shadow rounded-md p-4 max-w-sm w-full mx-auto">          
        ...
      </div>
    <% end %>   
  </div>  
<% end %>

ผลลัพธ์ที่ได้ก็จะเป็นดังแสดงในรูปด้านล่าง จะเห็นได้ว่าข้อมูลจะค่อยๆ ทยอยแสดงขึ้นทีละส่วนตามที่ต้องการ

demo.gif


ทั้งนี้ทั้งนั้น
สิ่งที่ทำมาทั้งหมดจริงๆ แล้ว Turbo Frame ของเรารองรับการแสดงข้อมูลแบบ Lazy Loading อยู่แล้ว ไม่จำเป็นต้องใช้ Stimulus เข้ามาช่วยแต่อย่างใด เพียงแค่เราระบุ src และ loading เป็น lazy เท่านั้นเอง

# demo.html.erb

...
<%= turbo_frame_tag "graph_#{index}", src: snippet_path(id: index), loading: "lazy" do %>
...