Disqus เป็น service ที่ทำให้เราสามารถ embed comments ในบล็อกหรือเว็บไซต์ที่เป็น static HTML ได้
หลักการก็คือเราแปะ universal embed code ของ Disqus ที่สร้าง <script>
element แค่อันเดียวลงไปใน template
แล้ว script ตัวนั้นมันก็จะ generate ทั้ง comment form
รวมถึง comment list ให้เสร็จ
แต่จริงๆ ไฟล์ที่ถูกโหลดมันไม่ได้มีแค่ไฟล์นี้ไฟล์เดียว เพราะไฟล์นี้มันก็ไปโหลดไฟล์อื่นๆ มาอีก
ผู้พัฒนา Disqus ชี้แจงใน css-tricks.comว่า
ไฟล์อื่นๆ ที่โหลดเพิ่มเข้ามา จะไม่ทำให้การ render ช้าลง (ถูกโหลดแบบ async อยู่หลัง event DOMContentLoaded
)
และไฟล์ส่วนมากก็อยู่บน CDN ของ Disqus ซึ่งก็อาจจะถูก browser cache ไว้อยู่แล้ว กรณีที่เราเคยเปิดเว็บอื่นที่มี Disqus comment เหมือนกัน
ถึงแม้อาจจะมีผลกระทบเรื่อง performance ไม่มากนัก แต่ผมก็ยังคิดว่ามันคงจะดีกว่า ถ้าเราให้ user โหลดมันเมื่อจำเป็นต้องใช้เท่านั้น
นั่นก็คือ เมื่อ user scroll ลงไปถึงกล่อง comment ด้านล่าง ถึงค่อยโหลด Disqus นั่นเอง
IntersectionObserver
เราสามารถใช้ IntersectionObserver
เพื่อบอกว่า ให้จับตามอง (observe) กล่อง comment ไว้
ถ้าแกถูกเลื่อนขึ้นมาอยู่ใน viewport เมื่อไหร่ ให้ไปเรียก callback function ที่เตรียมไว้นะ
callback function ที่เตรียมไว้จะเป็นอะไรก็ได้ ในกรณีของผมก็คือ function สำหรับการโหลด Disqus comment
ถ้าเป็นเมื่อก่อน เราอาจต้องใช้ scroll event ร่วมกับ event handler function โดยการเทียบค่า scroll offset ของทั้งตัว element เอง และ
window
object เพื่อเช็คว่า มันเข้ามาอยู่ใน viewport แล้วหรือยัง วุ่นวายพอสมควร (ตัวอย่าง jQuery, Vanilla JS)
1. เลือก target element ที่ต้องการ observe
การใช้ IntersectionObserver จะมี element ที่เกี่ยวข้อง 2 ตัวคือ
root
: element อ้างอิง หรือมองว่าเป็น “กรอบ” ก็ได้ จะเป็น element ที่เป็น scroll container หรือ viewport ก็ได้ครับtarget
: element ที่ต้องการ observe ว่ามันเลื่อนเข้าไปอยู่ในกรอบแล้วหรือยัง
ขั้นแรกเลือก target ก่อนเพื่อบอกว่า เราต้องการ observe เจ้ากล่อง comment นะ
const target = document.querySelector('#comments');
กล่อง comment ก็เป็น <div>
element ที่มี page-url
กับ identifier
ที่ Disqus จำเป็นต้องใช้
<div id="comments"
class="comments"
data-page-url="{{ .Permalink }}"
data-identifier="{{ .File.UniqueID }}">
<div id="disqus_thread"></div>
</div>
2. สร้าง observerOptions
object
เป็นตัวกำหนด behavior ของการ scroll
const observerOptions = {
root: null,
rootMargin: '250px 0px 0px',
threshold: 0
};
root: null
: element ที่เราต้องการมองเป็น “กรอบ” ใช้เป็นตัวอ้างอิงตำแหน่งการ scroll ส่วนการที่ให้ root เป็นnull
เพื่อบอกว่า ให้ document viewport ทำหน้าที่เป็นกรอบที่เราต้องการเช็คrootMargin: '250px 0px'
: เป็นการขยายขนาดของกรอบroot
เพื่อเผื่อระยะด้านบนและล่างไว้ 250px ทำให้ callback เริ่มทำงานก่อน scroll จะถึงตัว element จริงๆ (syntax เหมือน CSS margins/paddings แต่ว่าจำเป็นต้องใส่หน่วยให้กับ0px
)threshold: 0
: ให้ callback ทำงานเมื่อ element เริ่มโผล่เข้ามาใน viewport หรือ 0%
threshold
มีค่าตั้งแต่ 0 ถึง 1
- ถ้าให้เป็น 0 หมายถึง ให้เรียก callback เมื่อ element เข้ามาในกรอบ 0%
- ถ้าให้เป็น 1 หมายถึง ให้เรียก callback เมื่อ element เข้ามาในกรอบ 100%
3. สร้าง observer จาก IntersectionObserver
จากนั้นก็สร้าง object observer
จาก IntersectionObserver
โดยส่ง parameter เข้าไป 2 ตัว
const observer = new IntersectionObserver((entries, self) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// load disqus
}
});
}, observerOptions);
callback function
: function ที่จะถูกเรียกเมื่อ element เข้ามาอยู่ใน viewportobserverOptions
: object ที่เราสร้างไว้ในขั้นตอนก่อนหน้านี้
ข้อสังเกตก็คือ entries
ซึ่งเป็น argument แรก ของ callback function นั้นเป็น array
ส่วน argument ที่สอง self
ก็คือตัว observer object เอง
ใน callback function ก็วนลูปทุกๆ entries อีกที แล้วเช็ค property isIntersecting
(boolean)
ที่จะมีค่าเป็น true
เมื่อ element เข้ามาใน viewport ตามเงื่อนไขต่างๆ ที่เรากำหนดไว้ใน observableOptions
4. Observer.observe()
สุดท้ายแล้วก็ต้องบอก observer
ว่าต้องไปจับ element ที่เลือกไว้ในขั้นตอน 1. ด้วย method observe()
observer.observe(target);
5. loadDisqus()
ย้าย embed code ของ Disqus มาไว้ใน function (ส่วน pageURL
กับ id
ก็เก็บมาจาก data attribute ของกล่อง comment ข้างบน)
function loadDisqus(pageURL, id) {
window.disqus_config = function() {
this.page.url = pageURL;
this.page.identifier = id;
};
(function() {
// DON'T EDIT BELOW THIS LINE - ¯\_(ツ)_/¯
var d = document,
s = d.createElement('script');
s.src = 'https://armnointh.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
}
code ทั้งหมดรวมกัน
เนื่องจาก IntersectionObserver จะทำงานทั้งเลื่อนขึ้นและเลื่อนลง และทำงานไปเรื่อยๆ ถ้า element นั้นผ่านไปผ่านมาใน viewport
แต่เราจำเป็นต้องเรียก loadDisqus()
แค่ครั้งเดียว ถ้าโหลดแล้วเราก็ไม่จำเป็นต้อง observe อีกต่อไปให้สิ้นเปลือง
เรียก method self.unobserve()
เพื่อหยุดการทำงานของ observer
const commentsElement = document.querySelector('#comments');
if (!commentsElement) {
return;
}
const observerOptions = {
root: null,
rootMargin: '250px 0px 0px',
threshold: 0
};
const pageURL = commentsElement.getAttribute('data-page-url');
const identifier = commentsElement.getAttribute('data-identifier');
const observer = new IntersectionObserver(
(entries, self) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.info('loading comments');
loadDisqus(pageURL, identifier);
self.unobserve(commentsElement);
}
});
},
observerOptions
);
observer.observe(commentsElement);
function loadDisqus(pageURL, id) {
window.disqus_config = function() {
this.page.url = pageURL;
this.page.identifier = id;
};
(function() {
// DON'T EDIT BELOW THIS LINE
var d = document,
s = d.createElement('script');
s.src = 'https://armnointh.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
}
(หรือดู main.js ใน repo ก็ได้ครับ)
ผลลัพธ์
resource ของ Disqus จะถูกโหลดเมื่อ scroll ลงไปถึงกล่อง comment ข้างล่าง
มี delay นิดหน่อยในระหว่างที่ Disqus กำลังโหลด (เพิ่ม rootMargin
ด้านบนอีกได้ callback function จะได้เริ่มทำงานไวกว่าเดิม)
ลองใช้ Lighthouse audit ดู (Mobile, 4G, 4x CPU slowdown) ผลออกมาไม่ห่างกันมาก แต่ที่น่าสนใจคือตรงที่ล้อมกรอบไว้ การไม่โหลด Disqus ตั้งแต่แรก ลดเวลา Time to Interactive กับ Max Potential First Input Delay ได้นิดหน่อย เพราะ CPU ทำงานน้อยลงกว่าเดิม
⚠️ ข้อควรระวัง
มีข้อดีแล้วก็มีข้อที่ควรต้องระวังบ้าง
- direct link ไปยัง ID ของ comment จะหยุดทำงาน เช่น
https://url.com/#comment-8374
เวลาเพจโหลดเสร็จ มันจะไม่เลื่อนลงไปหา comment ให้เอง เนื่องจากมันยังไม่ได้โหลด - SEO: comment ของ Disqus นั้นถูก googlebot index ได้ และถึงแม้ว่า googlebot เข้าใจ IntersectionObserver แล้ว แต่ผมไม่แน่ใจว่ามัน scroll เป็นด้วยไหม จึงมีโอกาสทำให้ googlebot พลาดการ index content ของ comment ใน Disqus ได้
- IE11 ไม่ support IntersectionObserver แต่ผมลองเทสต์ใน Edge ดูแล้วไม่มีปัญหาอะไร
ดังนั้นถ้าอยากเอา IntersectionObserver ไปใช้งานจริงจัง ก็ต้องหาทาง support ส่วนที่หายไปอีกทีครับ
สำหรับบล็อกโนเนมแบบบล็อกนี้ ทั้ง 3 ข้อถือว่าไม่เป็นปัญหาเลย เพราะปกติไม่ค่อยมีคน comment อยู่แล้ว 😅