Cache the size for embeds that have dynamic sizes (Imgur, Reddit, TikTok, Twitter/X)

Hopefully it should improve [Cumulative Layout Shift](https://web.dev/articles/cls) for embeds that were loaded previously.

Also fixes https://github.com/GnoxNahte/obsidian-auto-embed/issues/4#issuecomment-2226989583 which was caused by TikTok not sending the resize message sometimes, especially if the time between each request is very short.
This commit is contained in:
GnoxNahte 2024-07-14 12:03:21 +08:00
parent 7c29dd1398
commit ca12faa75d
6 changed files with 76 additions and 29 deletions

View file

@ -1,4 +1,5 @@
import AutoEmbedPlugin from "src/main";
import { Dictionary, Size } from "src/utility";
export class BaseEmbedData {
embedSource: EmbedBase;
@ -26,9 +27,14 @@ export abstract class EmbedBase {
// If the website doesn't need / doesn't send the message, it'll be undefined.
readonly embedOrigin?: string;
readonly plugin: AutoEmbedPlugin;
// Cache the height of the embed.
// Correctly sizes the embeds that it has seen before as soon as the embed is loaded. Reducing Cumulative Layout Shift.
// Helps also when some websites don't send the resize message for some reason.
sizeCache: Dictionary<Size>;
constructor(plugin: AutoEmbedPlugin) {
this.plugin = plugin;
this.sizeCache = {};
}
abstract createEmbed(link: string, embedData?: BaseEmbedData): HTMLElement;
@ -88,7 +94,7 @@ export abstract class EmbedBase {
// To have a embed source respond to the resize event:
// - Set EmbedBase.embedOrigin (e.g. embedOrigin = "https://platform.twitter.com")
// - Set body of resize method here (onResizeMessage)
// - Implement body of resize method here (onResizeMessage)
onResizeMessage?(e: MessageEvent):void;
onErrorCreatingEmbed(msg?: string): HTMLElement {

View file

@ -18,7 +18,15 @@ export class ImgurEmbed extends EmbedBase {
iframe.src = `https://imgur.com/a/${imgurId}/embed?pub=true`;
iframe.classList.add(this.autoEmbedCssClass, "imgur-embed", "imgur-" + imgurId);
iframe.classList.add(this.autoEmbedCssClass, "imgur-embed");
iframe.dataset.imgurId = imgurId;
if (this.sizeCache[imgurId] && this.sizeCache[imgurId].height) {
iframe.style.height = this.sizeCache[imgurId].height + "px";
// Optional
if (this.sizeCache[imgurId].width)
iframe.style.width = this.sizeCache[imgurId].width + "px";
}
iframe.setAttribute("scrolling", "no");
@ -34,10 +42,10 @@ export class ImgurEmbed extends EmbedBase {
return;
const imgurId = regexMatch[1];
// Why use class instead of id for getting the reference:
// Why use querySelectorAll instead of querySelector for getting the reference:
// There might be multiple iframes, some in Reading mode and Live preview.
// Some might even duplicates if they user has duplicates
const iframes = document.getElementsByClassName("imgur-" + imgurId);
const iframes = document.querySelectorAll(`.imgur-embed[data-imgur-id="${imgurId}"`);
if (iframes.length === 0)
return;
@ -48,6 +56,8 @@ export class ImgurEmbed extends EmbedBase {
if (data.message === "resize_imgur") {
iframe.height = data.height + "px";
iframe.width = data.width + "px";
this.sizeCache[imgurId] = { width: data.width, height: data.height };
}
// ===== NOTE =====
// Imgur has different embed urls for "albums" and "posts"

View file

@ -11,19 +11,19 @@ export class RedditEmbed extends EmbedBase {
if (regexMatch === null)
return this.onErrorCreatingEmbed();
const postId = url.match(/(?:\/comments\/)(\w+)/) as RegExpMatchArray;
if (!postId)
const postIdRegexResult = url.match(/(?:\/comments\/)(\w+)/) as RegExpMatchArray;
if (!postIdRegexResult)
{
return this.onErrorCreatingEmbed();
}
const postId = postIdRegexResult[1];
// Creating the iframe
const iframe = createEl("iframe");
iframe.classList.add(this.autoEmbedCssClass, "reddit-embed", "reddit-" + postId[1]);
url = url.replace("www.reddit.com", "reddit.com"); // Remove "www"
url = url.replace("reddit.com", "embed.reddit.com"); // Add embed subdomain
url = url.replace("www.reddit.com", "embed.reddit.com"); // Remove "www"
if (this.plugin.settings.darkMode)
{
@ -32,20 +32,14 @@ export class RedditEmbed extends EmbedBase {
}
iframe.src = url;
iframe.setAttribute("scrolling", "no");
iframe.setAttribute("allowfullscreen", "");
// TODO: Dynamically set iframe height:
// Methods:
// - Listen to reddit's postmessage, reddit sends out a postmessage containing:
// {
// type: "resize.embed"
// data: [height value]
// }
// But it doesn't send the post id, so I'm not sure which iframe to set it to.
// - Get height from reddit api? Not sure how to do
// - Get content type from reddit api? Main heights are:
// - short text: 240px
// - long text (has a show more dropdown): 316px
// - picture / video: 739px
// console.log("Cache: " + JSON.stringify(this.sizeCache))
iframe.dataset.redditPostId = postId;
if (this.sizeCache[postId] && this.sizeCache[postId].height) {
iframe.style.height = this.sizeCache[postId].height + "px";
}
// iframe.style.height="unset";
@ -72,7 +66,13 @@ export class RedditEmbed extends EmbedBase {
// Check where the message came from
if (iframe.contentWindow == e.source)
{
iframe.style.height = data.data + "px";
const height = data.data;
iframe.style.height = height + "px";
const postId = iframe.dataset.redditPostId;
if (postId)
this.sizeCache[postId] = { width: 0, height: height};
break;
}
// console.log("Height: " + iframe.style.height);

View file

@ -3,7 +3,7 @@ import { EmbedBase } from "./embedBase";
export class TikTokEmbed extends EmbedBase {
name = "TikTok";
embedOrigin = "https://www.tiktok.com"
regex = new RegExp(/https:\/\/www\.tiktok\.com\/\@([\w\.]+)\/video\/(\d+)/);
regex = new RegExp(/https:\/\/www\.tiktok\.com\/@([\w.]+)\/video\/(\d+)/);
createEmbed(url: string): HTMLElement {
const regexMatch = url.match(this.regex);
@ -19,8 +19,13 @@ export class TikTokEmbed extends EmbedBase {
iframe.src = `https://www.tiktok.com/embed/v2/${tiktokId}/`;
iframe.classList.add(this.autoEmbedCssClass, "tiktok-embed");
// iframe.dataset.tiktokId = tiktokId;
iframe.setAttribute("allowfullscreen", "");
// console.log("Cache: " + JSON.stringify(this.sizeCache))
iframe.dataset.tiktokId = tiktokId;
if (this.sizeCache[tiktokId] && this.sizeCache[tiktokId].height) {
iframe.style.height = this.sizeCache[tiktokId].height + "px";
}
return iframe;
}
@ -58,6 +63,11 @@ export class TikTokEmbed extends EmbedBase {
if (iframe.contentWindow == e.source)
{
iframe.style.height = data.height + "px";
const tiktokId = iframe.dataset.tiktokId;
if (tiktokId)
this.sizeCache[tiktokId] = { width: 0, height: height};
break;
}

View file

@ -19,12 +19,17 @@ export class TwitterEmbed extends EmbedBase {
url = `https://platform.twitter.com/embed/Tweet.html?dnt=true&theme=${this.plugin.settings.darkMode ? "dark" : "light"}&id=${postId}`;
iframe.src = url;
iframe.classList.add(this.autoEmbedCssClass, "twitter-embed", "twitter-" + postId);
iframe.classList.add(this.autoEmbedCssClass, "twitter-embed");
iframe.sandbox.add("allow-forms", "allow-presentation", "allow-same-origin", "allow-scripts", "allow-modals", "allow-popups");
iframe.setAttribute("scrolling", "no");
// iframe.setAttribute("allowfullscreen", "");
iframe.dataset.twitterPostId = postId;
if (this.sizeCache[postId] && this.sizeCache[postId].height) {
iframe.style.height = this.sizeCache[postId].height + "px";
}
return iframe;
}
@ -46,17 +51,20 @@ export class TwitterEmbed extends EmbedBase {
}
*/
// console.log("Twitter Message: " + JSON.stringify(e.data["twttr.embed"]))
// Only continue if the method is for resizing
if (e.data["twttr.embed"]["method"] !== "twttr.private.resize")
return;
const params = e.data["twttr.embed"]["params"][0];
const postId = params["data"]["tweet_id"];
// console.log("Tweet-id: " + params["data"]["tweet_id"]);
// Why use class instead of id for getting the reference:
// Why use querySelectorAll instead of querySelector for getting the reference:
// There might be multiple iframes, some in Reading mode and Live preview.
// Some might even duplicates if they user has duplicates
const iframes = document.getElementsByClassName("twitter-" + params["data"]["tweet_id"]);
// console.log("Tweet-id: " + params["data"]["tweet_id"]);
const iframes = document.querySelectorAll(`.twitter-embed[data-twitter-post-id="${postId}"]`);
if (iframes.length === 0)
return;
@ -64,8 +72,12 @@ export class TwitterEmbed extends EmbedBase {
for (let i = 0; i < iframes.length; ++i) {
const iframe = iframes[i] as HTMLIFrameElement;
iframe.style.height = ((params["height"] as number) + 1) + "px";
const height = (params["height"] as number) + 1;
// iframe.style.width = ((params["width"] as number) + 1) + "px";
iframe.style.height = height + "px";
if (postId)
this.sizeCache[postId] = { width: 0, height: height};
}
}
}

View file

@ -11,4 +11,13 @@ export function isURL(str: string) : boolean {
export function isLinkToImage(url: string) : boolean {
return /\.(jpg|jpeg|png|webp|avif|gif)$/.test(url);
}
export interface Dictionary<T> {
[key: string]: T;
}
export interface Size {
width: number;
height: number;
}