Skip to content

Commit

Permalink
Lazy load embeds (especially tweets) (#322)
Browse files Browse the repository at this point in the history
* lazy load tweets, tiktok embeds, and instagram embeds (manually attach scripts when loading)
* don't lazy load unhandled embeds (because scripts won't run)
  • Loading branch information
walsh9 authored Oct 10, 2023
1 parent 672c24f commit fedc386
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 62 deletions.
38 changes: 20 additions & 18 deletions components/LoadLazily.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
<script setup lang="ts">
const props = withDefaults(defineProps<{
options?: {
root?: HTMLElement
rootMargin?: string
threshold?: number | number[]
}
}>(), {
options: {
const props = withDefaults(defineProps<{
options?: {
root?: HTMLElement
rootMargin?: string
threshold?: number | number[]
}
}>(), {
options() {
return {
root: null,
rootMargin: '1000px 0px 0px 0px',
threshold: 0
rootMargin: '0px 0px 400px 0px',
threshold: 0,
}
})
const element = ref(null)
const seen = ref(false)
const onVisible = () => {seen.value = true}
const onNotVisible = () => { /*do nothing*/ }
useVisibilityTracking(element, onVisible, onNotVisible, props.options )
},
})
const element = ref(null)
const seen = ref(false)
function onVisible() { seen.value = true }
function onNotVisible() { /* do nothing */ }
useVisibilityTracking(element, onVisible, onNotVisible, props.options)
</script>

<template>
<div ref="element">
<div v-if="seen">
<slot/>
<slot />
</div>
</div>
</template>
</template>
60 changes: 47 additions & 13 deletions components/Streamfield/StreamfieldEmbed.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,65 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { EmbedBlock } from '../../composables/types/StreamfieldBlock'
import { computed } from 'vue'
import type { EmbedBlock } from '../../composables/types/StreamfieldBlock'
const props = defineProps<{
block: EmbedBlock
}>()
const embedType = computed(() => {
if (props.block.value.embed.includes('blockquote class="twitter-tweet"')) {
if (props.block.value.embed.includes('blockquote class="twitter-tweet"'))
return 'twitter-tweet'
}
else if (props.block.value.embed.includes('https://www.youtube.com/embed')) {
else if (props.block.value.embed.includes('blockquote class="tiktok-embed"'))
return 'tiktok-embed'
else if (props.block.value.embed.includes('blockquote class="instagram-media"'))
return 'instagram-media'
else if (props.block.value.embed.includes('https://www.youtube.com/embed'))
return 'youtube-video'
}
return 'default'
})
const minHeight = computed(() => {
let height: string
switch (embedType.value) {
case 'tiktok-embed':
height = '740'
break
case 'instagram-media':
height = '620'
break
default:
height = '200'
}
return height
})
</script>

<template>
<StreamfieldEmbedTweet
v-if="embedType === 'twitter-tweet'"
:key="`${block.id}-embed-tweet`"
:block="block"
/>
<StreamfieldEmbedDefault
v-else
:class="[{ youtube: embedType === 'youtube-video' }]"
v-if="embedType === 'default' || embedType === 'youtube-video'"
:key="`${block.id}-embed-default`"
:class="[{ youtube: embedType === 'youtube-video' }]"
:block="block"
/>
<LoadLazily
v-else
:style="`min-height: ${minHeight}px`"
>
<LazyStreamfieldEmbedTweet
v-if="embedType === 'twitter-tweet'"
:key="`${block.id}-embed-tweet`"
:block="block"
/>
<LazyStreamfieldEmbedTiktok
v-else-if="embedType === 'tiktok-embed'"
:key="`${block.id}-tiktok-embed`"
:block="block"
/>
<LazyStreamfieldEmbedInstagram
v-else-if="embedType === 'instagram-media'"
:key="`${block.id}-instagram-media`"
:block="block"
/>
</LoadLazily>
</template>
7 changes: 1 addition & 6 deletions components/Streamfield/StreamfieldEmbedDefault.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const el = ref(null)

<template>
<div
class="streamfield-embed streamfield-embed-defualt streamfield-paragraph mb-7"
class="streamfield-embed streamfield-embed-default streamfield-paragraph mb-7"
v-html="block.value.embed"
ref="el"
/>
Expand All @@ -24,10 +24,5 @@ const el = ref(null)
height: 100% !important;
}
}
.instagram-media {
@include media('<lg') {
margin: auto !important;
}
}
}
</style>
35 changes: 35 additions & 0 deletions components/Streamfield/StreamfieldEmbedInstagram.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { EmbedBlock } from '~~/composables/types/StreamfieldBlock';
import { ref } from 'vue'
defineProps<{
block: EmbedBlock
}>()
const el = ref(null)
onMounted(() => {
const js = document.createElement('script')
js.src = '//www.instagram.com/embed.js'
el.value.appendChild(js)
})
</script>

<template>
<div
class="streamfield-embed streamfield-embed- instagram streamfield-paragraph mb-7"
v-html="block.value.embed"
ref="el"
/>
</template>

<style lang="scss">
.streamfield-embed {
.instagram-media {
@include media('<lg') {
margin: auto !important;
}
}
}
</style>
25 changes: 25 additions & 0 deletions components/Streamfield/StreamfieldEmbedTiktok.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup lang="ts">
import { EmbedBlock } from '~~/composables/types/StreamfieldBlock';
import { ref } from 'vue'
defineProps<{
block: EmbedBlock
}>()
const el = ref(null)
onMounted(() => {
const js = document.createElement('script')
js.src = 'https://www.tiktok.com/embed.js'
el.value.appendChild(js)
})
</script>

<template>
<div
class="streamfield-embed streamfield-embed-tiktok streamfield-paragraph mb-7"
v-html="block.value.embed"
ref="el"
/>
</template>
22 changes: 21 additions & 1 deletion components/Streamfield/StreamfieldEmbedTweet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ import { usePreferredDark } from '@vueuse/core';
import { EmbedBlock } from '~~/composables/types/StreamfieldBlock';
import { computed, ref } from 'vue'
useHead({
script: [{
children: `window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = 'https://platform.twitter.com/widgets.js';
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, 'script', 'twitter-wjs'));`,
}]
})
const props = defineProps<{
block: EmbedBlock
}>()
Expand Down Expand Up @@ -83,4 +104,3 @@ onMounted(async () => {
}
}
</style>

11 changes: 7 additions & 4 deletions composables/useVisibilityTracking.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
export default function useVisibilityTracking(elementRef, onVisible, onNotVisible, options={threshold: [1.0]}) {
export default function useVisibilityTracking(elementRef, onVisible, onNotVisible, options) {
const componentIsVisible = ref()
options = options ?? {}
options.threshold = options.threshold ?? [1.0]

if (!process.server) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && componentIsVisible.value !== true) {
componentIsVisible.value = true
onVisible()
} else if (!entry.isIntersecting && componentIsVisible.value !== false) {
}
else if (!entry.isIntersecting && componentIsVisible.value !== false) {
componentIsVisible.value = false
onNotVisible()
}
})
}, options);
}, options)

onMounted(() => {
onMounted(() => {
observer.observe(elementRef.value)
})

Expand Down
26 changes: 6 additions & 20 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,25 +129,7 @@ useHead({
{
src: config.public.HTL_JS,
async: true,
},
{
children: `window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = 'https://platform.twitter.com/widgets.js';
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, 'script', 'twitter-wjs'));`,
},
}
]
})
Expand Down Expand Up @@ -264,7 +246,11 @@ if (isSponsoredRoute) {
</div>
</template>
<template v-slot:default>
<LoadLazily>
<LoadLazily :options="{
root: null,
rootMargin: '0px',
threshold: 0
}">
<LazyGothamistSidebarContents
:navigation="navigation"
:donateUrlBase="config.public.donateUrlBase"
Expand Down

0 comments on commit fedc386

Please sign in to comment.