From 760477f00c40b6f635c90c49d07fb549dbd805b2 Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Sat, 7 Sep 2024 12:36:21 +1200 Subject: [PATCH] use JS IntersectionObserver to lazy load youtube videos --- app/models.py | 8 +++-- app/static/js/scripts.js | 37 ++++++++++++++++++++++ app/static/styles.css | 25 +++++++++++++++ app/static/styles.scss | 27 ++++++++++++++++ app/templates/post/post_teaser/_video.html | 4 ++- 5 files changed, 97 insertions(+), 4 deletions(-) diff --git a/app/models.py b/app/models.py index 9f97527f..b80c01d7 100644 --- a/app/models.py +++ b/app/models.py @@ -1072,14 +1072,15 @@ class Post(db.Model): file = File.query.get(self.image_id) file.delete_from_disk() - def youtube_embed(self) -> str: + def youtube_embed(self, rel=True) -> str: if self.url: parsed_url = urlparse(self.url) query_params = parse_qs(parsed_url.query) if 'v' in query_params: video_id = query_params.pop('v')[0] - query_params['rel'] = '0' + if rel: + query_params['rel'] = '0' new_query = urlencode(query_params, doseq=True) return f'{video_id}?{new_query}' @@ -1087,7 +1088,8 @@ class Post(db.Model): video_id = parsed_url.path.split('/shorts/')[1].split('/')[0] if 't' in query_params: query_params['start'] = query_params.pop('t')[0] - query_params['rel'] = '0' + if rel: + query_params['rel'] = '0' new_query = urlencode(query_params, doseq=True) return f'{video_id}?{new_query}' diff --git a/app/static/js/scripts.js b/app/static/js/scripts.js index cae6a9a3..cdbcc501 100644 --- a/app/static/js/scripts.js +++ b/app/static/js/scripts.js @@ -10,6 +10,7 @@ if(!setTheme) { // fires after DOM is ready for manipulation document.addEventListener("DOMContentLoaded", function () { + setupYouTubeLazyLoad(); setupCommunityNameInput(); setupShowMoreLinks(); setupConfirmFirst(); @@ -27,6 +28,42 @@ document.addEventListener("DOMContentLoaded", function () { setupLightboxPostBody(); }); +function setupYouTubeLazyLoad() { + const lazyVideos = document.querySelectorAll(".video-wrapper"); + + if ("IntersectionObserver" in window) { + let videoObserver = new IntersectionObserver((entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + let videoWrapper = entry.target; + let iframe = document.createElement("iframe"); + iframe.src = videoWrapper.getAttribute("data-src"); + iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"; + + videoWrapper.innerHTML = ""; + videoWrapper.appendChild(iframe); + + videoObserver.unobserve(videoWrapper); + } + }); + }); + + lazyVideos.forEach((video) => { + videoObserver.observe(video); + }); + } else { + // Fallback for older browsers + lazyVideos.forEach((video) => { + let iframe = document.createElement("iframe"); + iframe.src = video.getAttribute("data-src"); + iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"; + + video.innerHTML = ""; + video.appendChild(iframe); + }); + } +} + // All elements with the class "showElement" will show the DOM element referenced by the data-id attribute function setupShowElementLinks() { var elements = document.querySelectorAll('.showElement'); diff --git a/app/static/styles.css b/app/static/styles.css index 0311cbce..a069799c 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -911,6 +911,31 @@ div.navbar { background-color: white; opacity: 0.5; } +.post_teaser_video_preview .video-wrapper { + position: relative; + padding-bottom: 56.25%; + /* 16:9 aspect ratio */ + height: 0; + overflow: hidden; + background-color: #000; +} +.post_teaser_video_preview .video-wrapper img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + cursor: pointer; +} +.post_teaser_video_preview .video-wrapper iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} .max_width_512 { max-width: 512px; diff --git a/app/static/styles.scss b/app/static/styles.scss index 28eda2e4..06754e04 100644 --- a/app/static/styles.scss +++ b/app/static/styles.scss @@ -503,6 +503,33 @@ div.navbar { background-color: white; opacity: 0.5; } + + .video-wrapper { + position: relative; + padding-bottom: 56.25%; /* 16:9 aspect ratio */ + height: 0; + overflow: hidden; + background-color: #000; + } + + .video-wrapper img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + cursor: pointer; + } + + .video-wrapper iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } } .max_width_512 { diff --git a/app/templates/post/post_teaser/_video.html b/app/templates/post/post_teaser/_video.html index 3f054939..04cc26e3 100644 --- a/app/templates/post/post_teaser/_video.html +++ b/app/templates/post/post_teaser/_video.html @@ -43,7 +43,9 @@
{% endif -%} {% if 'youtube.com' in post.url -%} - +