<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ryo</title>
    <description>The latest articles on DEV Community by Ryo (@ryo-karasunouta).</description>
    <link>https://dev.to/ryo-karasunouta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4014798%2F69c40c00-431b-4bb0-8258-4642d33ad8f3.png</url>
      <title>DEV Community: Ryo</title>
      <link>https://dev.to/ryo-karasunouta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ryo-karasunouta"/>
    <language>en</language>
    <item>
      <title>When WordPress Admin Menu Items Overflowed, I Built a Plugin to Folder Them</title>
      <dc:creator>Ryo</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/ryo-karasunouta/when-wordpress-admin-menu-items-overflowed-i-built-a-plugin-to-folder-them-81g</link>
      <guid>https://dev.to/ryo-karasunouta/when-wordpress-admin-menu-items-overflowed-i-built-a-plugin-to-folder-them-81g</guid>
      <description>&lt;p&gt;When running a WordPress site, there comes a moment when you suddenly realize something.&lt;/p&gt;

&lt;p&gt;"Wait... is my side menu way too long?"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqb6o1ki8qp68m2g1qhbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqb6o1ki8qp68m2g1qhbp.png" alt="Illustration of a shocked woman (a classic illustration from Irasutoya)" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;p&gt;&lt;em&gt;Wait...&lt;/em&gt;&lt;/p&gt;&lt;/center&gt;

&lt;p&gt;In its initial state, the WordPress admin menu neatly arranges essential items in a compact list: Posts, Media, Pages, Appearance, Plugins, Settings, and so on. But as you run the site longer—adding features, integrating external services, and beefing up SEO and security—it swells to a daunting length that makes you weary just looking at it.&lt;/p&gt;

&lt;p&gt;This site had fallen into that exact state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxog8hd2a1vik550ui8zd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxog8hd2a1vik550ui8zd.png" alt="Screenshot of the WordPress admin dashboard. The left side menu is bloated with numerous items, filling the entire vertical height of the page. An arrow extends from it toward the right main pane, with large red text reading " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;p&gt;&lt;em&gt;Isn't this way too long?&lt;/em&gt;&lt;/p&gt;&lt;/center&gt;

&lt;p&gt;Between writing sessions, whenever I'd think, "Let me tweak this one setting," or "Where was that plugin's settings menu again?", I had to scan down the endless side menu. If I couldn't spot it, I'd expand "Tools" or "Settings" to search, and still come up empty. Only upon looking very carefully would I find it sitting all alone near the bottom of the sidebar—.&lt;/p&gt;

&lt;p&gt;As these repeated micro-stresses accumulated, my patience was wearing thin. What's more, &lt;strong&gt;the number of plugins is only likely to grow further from here&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can We Just Delete Unnecessary Plugins?
&lt;/h2&gt;

&lt;p&gt;"By the way, just how many plugins are installed on this site right now?"&lt;/p&gt;

&lt;p&gt;Thinking this, I counted the list of plugins in the admin screen, and here are the ones that were active at the time:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Click to view the plugin list
  &lt;ul&gt;
&lt;li&gt;APCu Manager&lt;/li&gt;
&lt;li&gt;Better Search Replace&lt;/li&gt;
&lt;li&gt;Block AI Crawlers&lt;/li&gt;
&lt;li&gt;CompressX - AVIF &amp;amp; WebP Converter&lt;/li&gt;
&lt;li&gt;Contact Form 7&lt;/li&gt;
&lt;li&gt;Database Manager - WP Adminer&lt;/li&gt;
&lt;li&gt;Dev Favicon Switcher&lt;/li&gt;
&lt;li&gt;Duplicate Page&lt;/li&gt;
&lt;li&gt;Enable Media Replace&lt;/li&gt;
&lt;li&gt;GenerateBlocks&lt;/li&gt;
&lt;li&gt;Highlighting Code Block&lt;/li&gt;
&lt;li&gt;Kadence Blocks — Page Builder Toolkit for Gutenberg Editor&lt;/li&gt;
&lt;li&gt;KU Sticky Video for YouTube&lt;/li&gt;
&lt;li&gt;KU Sticky Video for YouTube Pro&lt;/li&gt;
&lt;li&gt;Lazy Load for Videos&lt;/li&gt;
&lt;li&gt;Lemon Squeezy — Sell Digital Products, Subscriptions, and Licenses&lt;/li&gt;
&lt;li&gt;LH Archived Post Status&lt;/li&gt;
&lt;li&gt;Lightbox with PhotoSwipe&lt;/li&gt;
&lt;li&gt;LightStart - Maintenance Mode, Coming Soon and Landing Page Builder&lt;/li&gt;
&lt;li&gt;Mail logging - WP Mail Catcher&lt;/li&gt;
&lt;li&gt;Permalink Manager Lite&lt;/li&gt;
&lt;li&gt;Polylang&lt;/li&gt;
&lt;li&gt;Sassy Social Share&lt;/li&gt;
&lt;li&gt;Secondary Title&lt;/li&gt;
&lt;li&gt;Simple CAPTCHA with Cloudflare Turnstile&lt;/li&gt;
&lt;li&gt;Simple Local Avatars&lt;/li&gt;
&lt;li&gt;Site Kit by Google&lt;/li&gt;
&lt;li&gt;User Switching&lt;/li&gt;
&lt;li&gt;Wordfence Security&lt;/li&gt;
&lt;li&gt;WPS Hide Login&lt;/li&gt;
&lt;li&gt;WPvivid Backup Plugin&lt;/li&gt;
&lt;li&gt;WP Dark Mode&lt;/li&gt;
&lt;li&gt;Zoho Mail&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;33 plugins&lt;/strong&gt;. You might think that is quite a lot. In reality, though, running a full-fledged site often leaves you with no choice but to install around this many.&lt;br&gt;
At first, I wondered, "Couldn't I delete a few plugins to simplify the side menu?" In the end, I gave up on that idea.&lt;/p&gt;

&lt;p&gt;Most of them handle caching, image conversion, security, backups, email delivery, and other indispensable daily functions that simply cannot be removed. Furthermore, switching one or two plugins to a &lt;strong&gt;"keep them uninstalled and only install when needed" workflow is a hassle in itself&lt;/strong&gt;. And removing just one or two wouldn't make the side menu noticeably shorter anyway.&lt;/p&gt;

&lt;p&gt;Which means:&lt;br&gt;
If we cannot reduce the plugins, our only option is to rethink how the menu is presented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hopes for Existing Menu Management Plugins. But...
&lt;/h2&gt;

&lt;p&gt;Naturally, there are already several plugins available on WP.org (the official WordPress directory) and elsewhere designed to organize side menus. Of course, I researched them and tested them in a staging environment. Unfortunately, none of them made the cut.&lt;/p&gt;

&lt;p&gt;I won't name names since this might sound a bit critical, but roughly speaking, they suffered from the following issues (those who have tried them might recognize which ones I'm talking about):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Overly complex UI that isn't intuitive&lt;/strong&gt;&lt;br&gt;
Aiming for high functionality is commendable, but the settings screen becomes correspondingly bloated. When all you want to do is move a single menu item, you're left wondering what to click.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Relies on a three-tier side menu that is labeled "experimental" and lacks reliability&lt;/strong&gt;&lt;br&gt;
To properly categorize menus into folders, you have to enable a three-tier menu option labeled as an "experimental feature." However, &lt;strong&gt;the standard WordPress side menu architecture was only ever designed for two tiers&lt;/strong&gt;. As a result, layouts break, hover behavior gets glitchy, and it leaves too much uncertainty for production use (which is probably why it's labeled "experimental" in the first place!).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resorting to the extreme measure of replacing the entire side menu with a custom UI&lt;/strong&gt;&lt;br&gt;
The most aggressive plugins threw out WordPress's standard menu rendering mechanism entirely, replacing the whole side menu with custom markup. While this looks modern, it carries significant risks: conflicts with badges and scripts added by other plugins, unpredictable fallout when WordPress core updates, and so on. In the end, I concluded that &lt;strong&gt;I wouldn't feel comfortable using it myself, let alone in client work&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Such a Heavy-Handed Approach?
&lt;/h3&gt;

&lt;p&gt;The more I examined the source code and behavior of existing plugins, the more a question grew in my mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why does every single one of them resort to such heavy-handed measures?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If all you want is to declutter the WordPress admin side menu, there should be a much more natural and safer way. In fact, I had a very clear concept in mind of how to do it.&lt;br&gt;
That said, it was a straightforward design that any engineer familiar with WordPress internals could easily come up with. That was precisely why I assumed a plugin doing "exactly that" had to exist in the official directory—and that's what I went looking for.&lt;/p&gt;

&lt;p&gt;Strange as it was, it didn't exist. Once again, no matter how much I searched, I couldn't find "that exact thing."&lt;/p&gt;

&lt;p&gt;"If it doesn't exist, I'll build it myself."&lt;/p&gt;

&lt;p&gt;And that is how I came to develop my new &lt;a href="https://wordpress.org/plugins/karasunouta-admin-menu-folder/" rel="noopener noreferrer"&gt;Karasunouta Admin Menu Folder plugin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Don't Need a 3-Tier Menu
&lt;/h2&gt;

&lt;p&gt;The principle is extremely simple. To begin with, there is no need to force a three-tier side menu structure that disregards WordPress's native design.&lt;/p&gt;

&lt;p&gt;By default, WordPress's plugin menu structure is designed so that when you access a root (parent) menu, its submenu (child) items automatically expand beneath it. Within the scope of two tiers, WordPress officially provides standard hooks to organize menu items. In that case, the solution is simple. You just do this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fml9msxaxymh3vwyawp6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fml9msxaxymh3vwyawp6j.png" alt="Screenshot of the initial state of the Karasunouta Admin Menu Folder plugin settings page. The left side menu stretches long from top to bottom." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc9jdf8l1otqyev935ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc9jdf8l1otqyev935ly.png" alt="Screenshot of the Karasunouta Admin Menu Folder plugin settings page after configuration. The display in the right main pane has changed significantly, leaving a large empty space near the bottom of the left side menu as the number of items decreased. A root item named " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove root menus of infrequently used plugins from the side menu, and&lt;/li&gt;
&lt;li&gt;Display them instead as submenus within a newly added Menu Folder in the side menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's really all it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Behavior of Stowed Plugins: The Case of LightStart
&lt;/h3&gt;

&lt;p&gt;Take the LightStart plugin, for instance, which provides maintenance mode protection for your site. It is an extremely useful, indispensable plugin for me, but it's only needed during major maintenance work—just a few times a year.&lt;br&gt;
There is no reason for such a plugin's root menu to sit permanently in the side menu all year round. It was the very first candidate I stowed into the Karasunouta Admin Menu Folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3oc793510tcs96gqvd0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3oc793510tcs96gqvd0h.png" alt="Screenshot near the bottom left of the WordPress admin screen. The Menu Folder root menu is expanded, showing " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;p&gt;&lt;em&gt;Here it is&lt;/em&gt;&lt;/p&gt;&lt;/center&gt;

&lt;p&gt;The root menu of the LightStart plugin stowed in the Menu Folder appears as shown in the image above. You can open the LightStart settings page from here, so there is never any issue when you want to use its primary maintenance mode feature.&lt;/p&gt;

&lt;h4&gt;
  
  
  What About Submenus?
&lt;/h4&gt;

&lt;p&gt;However, what about LightStart's submenu item, "About Us"? You might wonder: "What if I really want to open that, but the side menu only has 'Menu Folder &amp;gt; LightStart' and no 'Menu Folder &amp;gt; LightStart &amp;gt; About Us'? I can't access it—what now?!"&lt;br&gt;
Such a concern might cross your mind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9s94beuw2gy05anvdtkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9s94beuw2gy05anvdtkg.png" alt="Illustration of a troubled man (a classic illustration from Irasutoya)" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;p&gt;&lt;em&gt;Don't we really need a 3-tier menu after all?&lt;/em&gt;&lt;/p&gt;&lt;/center&gt;

&lt;p&gt;Don't worry. Remember what I wrote earlier:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, WordPress's plugin menu structure is designed so that when you access a root (parent) menu, its submenu (child) items automatically expand beneath it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's look at the actual behavior.&lt;/p&gt;

&lt;p&gt;The moment you click "Menu Folder &amp;gt; LightStart" and the LightStart settings page opens, the LightStart root menu reappears in the side menu. At this point, because the LightStart settings page is naturally "currently active," the LightStart root menu is automatically expanded by WordPress (= its submenu items become visible).&lt;/p&gt;

&lt;p&gt;If you actually navigate to the LightStart plugin settings page, you will see the side menu look like the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p7qr049suxygeo9p9mu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p7qr049suxygeo9p9mu.png" alt="Screenshot of the LightStart plugin settings page. The LightStart root menu is expanded in the left side menu, with " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;p&gt;&lt;em&gt;The "About Us" submenu is clearly visible too!&lt;/em&gt;&lt;/p&gt;&lt;/center&gt;

&lt;p&gt;As you can see, &lt;strong&gt;the moment the LightStart settings page opens, the LightStart root menu appears in the left side menu and expands&lt;/strong&gt;. As a result, you can seamlessly access the "LightStart &amp;gt; About Us" menu item.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary: How to Access Submenus
&lt;/h3&gt;

&lt;p&gt;In other words, even if you want to access a submenu item of a stowed plugin, all it takes is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, open the stowed menu item in the Menu Folder (e.g., "Menu Folder &amp;gt; LightStart")&lt;/li&gt;
&lt;li&gt;Next, open the temporarily expanded submenu in the side menu (e.g., "LightStart &amp;gt; About Us")&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;—allowing reliable access in just two clicks. After all, the Menu Folder is meant for "rarely used plugin root menus," which means their submenus are accessed even less frequently. If you can reach them in two clicks, I believe it is safe to say &lt;strong&gt;accessibility is not an issue&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Don't Need Risky Hacks, Either
&lt;/h2&gt;

&lt;p&gt;The crucial point here is that throughout this process, &lt;strong&gt;not a single fragile or heavy-handed trick was used&lt;/strong&gt;—such as the approaches seen in some other plugins, like completely replacing the side menu with React or shoehorning in an unsupported three-tier structure. Everything follows WordPress specifications. It simply toggles the display state of the side menu in a completely natural way, using only standard filter hooks provided officially by WordPress.&lt;/p&gt;

&lt;p&gt;Because it &lt;strong&gt;does not fight against WordPress's native architecture&lt;/strong&gt;, the operation remains fast and stable, and the risk of conflicts with other plugins is kept to an absolute minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Impact: Before and After
&lt;/h2&gt;

&lt;p&gt;While the approach is very simple, the results are striking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3jdpleimdbpcxfxuxbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3jdpleimdbpcxfxuxbf.png" alt="Screenshot of the WordPress admin dashboard. The menu list in the left sidebar spans the full vertical height of the image." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vn94fxvralryptjxiaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vn94fxvralryptjxiaf.png" alt="Screenshot of the WordPress admin dashboard. The menu list in the left side menu extends roughly two-thirds down the page. A thick red arrow points upward from the bottom, highlighting the compressed height of the left side menu." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...Can you see the difference? Just grouping infrequently accessed root menus into a single "Menu Folder" makes the admin interface so much cleaner.&lt;br&gt;
With a clear view of the side menu, you can instantly navigate to frequently used items like "Posts," "Pages," and "Appearance" without hesitation.&lt;/p&gt;

&lt;p&gt;A pleasant and somewhat unexpected byproduct was that finding "infrequently used menus" actually became faster too.&lt;br&gt;
My mind naturally adapted to the rule: &lt;strong&gt;"Don't use it often = probably in Menu Folder."&lt;/strong&gt; As a result, I started checking the Menu Folder first. And more often than not, it was right there, easily found. Truly. At least that was my experience, so I encourage you to try it for yourself.&lt;/p&gt;

&lt;p&gt;Consequently, whether an item is used constantly or rarely, &lt;strong&gt;accessibility to every menu item across the site improved dramatically&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Modest Setting You Won't Want to Live Without
&lt;/h2&gt;

&lt;p&gt;While the usability of my admin dashboard improved dramatically, there was one additional small feature I wanted as I used it in day-to-day work. That is the &lt;strong&gt;"Show link to settings page in toolbar"&lt;/strong&gt; option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7k5ictb49rc45owyl35z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7k5ictb49rc45owyl35z.png" alt="Screenshot of the Karasunouta Admin Menu Folder plugin settings page. The " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Checking this box adds a direct link to "Admin Menu Folder" in the toolbar (admin bar) at the top of the admin screen. It is a simple setting, but when you add a new plugin and feel the side menu getting cluttered again, or wonder, "Did I stow that menu in the folder yet?", being able to &lt;strong&gt;jump to the settings page in a single click from the admin bar&lt;/strong&gt; is surprisingly convenient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Although this plugin was only recently released, I personally believe it should be a &lt;strong&gt;must-have plugin&lt;/strong&gt; for future WordPress site builds. In fact, I now install and use it on all WordPress sites I handle, including client work as well as my own site. Going back to that previously cluttered side menu environment is something I can hardly imagine anymore.&lt;/p&gt;

&lt;p&gt;In particular, if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Are suffering from scroll hell due to an overwhelming number of plugins&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Want to deliver a clean, well-organized admin dashboard to your clients&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;—please give it a try. It should make your day-to-day site management surprisingly comfortable!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official Plugin Page (WordPress.org)&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://wordpress.org/plugins/karasunouta-admin-menu-folder/" rel="noopener noreferrer"&gt;Karasunouta Admin Menu Folder – WordPress plugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you find it useful, leaving a 5-star review would mean a lot to me!)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Safely Integrate the YouTube API in Lazy-Loading Environments Using MutationObserver</title>
      <dc:creator>Ryo</dc:creator>
      <pubDate>Mon, 20 Jul 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/ryo-karasunouta/how-to-safely-integrate-the-youtube-api-in-lazy-loading-environments-using-mutationobserver-1fhn</link>
      <guid>https://dev.to/ryo-karasunouta/how-to-safely-integrate-the-youtube-api-in-lazy-loading-environments-using-mutationobserver-1fhn</guid>
      <description>&lt;p&gt;In the recently published article “&lt;a href="https://karasunouta.com/en/web-tech/how-to-load-the-youtube-iframe-player-api-without-breaking-other-plugins/" rel="noopener noreferrer"&gt;How to Load the YouTube IFrame Player API Without Breaking Other Plugins&lt;/a&gt;,” we discussed how to prevent conflicts in environments where multiple plugins attempt to initialize the YouTube API simultaneously by incorporating a spirit of mutual concession (polling, sharing existing players, and random jitter) into the code.&lt;/p&gt;

&lt;p&gt;The WordPress plugin “&lt;a href="https://karasunouta.com/en/wp-plugins/ku-sticky-video-for-youtube/" rel="noopener noreferrer"&gt;KU Sticky Video for YouTube&lt;/a&gt;,” which incorporates this design, had been operating smoothly on this site without any conflict issues. However, a user reported that &lt;strong&gt;the sticky video feature stopped working when used in combination with lazy-loading (LazyLoad) plugins&lt;/strong&gt;. Upon verification, this was indeed the case!&lt;/p&gt;

&lt;p&gt;The polling method discussed in the previous article assumes that the video player (&lt;code&gt;iframe&lt;/code&gt;) itself already exists in a static state within the DOM at the time of page load. However, in a lazy-loading environment where video players are loaded dynamically in response to user actions such as scrolling or clicking, the target &lt;code&gt;iframe&lt;/code&gt; has not yet been generated at page load. Admittedly, this was a blind spot.&lt;/p&gt;

&lt;p&gt;This article explains a dynamic and asynchronous binding design for the YouTube API using &lt;code&gt;MutationObserver&lt;/code&gt; to overcome the new challenge of coexisting with LazyLoad plugins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Does It Not Work? The YouTube API Under LazyLoad
&lt;/h2&gt;

&lt;p&gt;To improve initial page load performance, LazyLoad plugins delay the loading of images and &lt;code&gt;iframe&lt;/code&gt;s using approaches such as the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Attribute rewriting:&lt;/strong&gt; The plugin assigns a placeholder or empty value to the &lt;code&gt;src&lt;/code&gt; attribute, moving the actual URL to a separate attribute such as &lt;code&gt;data-src&lt;/code&gt;. Once the element approaches the viewport during scrolling, JavaScript writes the actual URL back to the &lt;code&gt;src&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dynamic DOM insertion:&lt;/strong&gt; The &lt;code&gt;iframe&lt;/code&gt; element itself is not generated in the DOM until it enters the viewport. Instead, a placeholder image of the video player is displayed. When the play button on the placeholder image is clicked, the &lt;code&gt;iframe&lt;/code&gt; is dynamically generated and displayed in its place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In contrast, many YouTube-related plugins, including KU Sticky Video for YouTube, execute &lt;code&gt;document.querySelectorAll('iframe')&lt;/code&gt; at timings such as &lt;code&gt;DOMContentLoaded&lt;/code&gt; to perform initialization and register event listeners on the elements existing at that moment. However, when LazyLoad is enabled, the iframe elements for embedded YouTube videos are either not yet generated at this timing, or are intentionally generated in an incomplete state.&lt;/p&gt;

&lt;p&gt;Plugins dependent on the YouTube API attempt to search for target iframes under these conditions. However, no matching iframes are found. As a result, the initialization process is skipped, and the plugin’s functionality—in the case of KU Sticky Video for YouTube, the sticky video feature that follows the scroll—never triggers.&lt;/p&gt;

&lt;p&gt;The polling-based countermeasure implemented in the previous article was also largely ineffective against LazyLoad plugins. Since polling begins immediately after page load, the delay in execution is at most a few seconds. Meanwhile, it is impossible to predict when a reader will scroll down the article. While one could theoretically poll indefinitely, &lt;strong&gt;polling requires periodic timer events&lt;/strong&gt;, making indefinite waiting impractical.&lt;/p&gt;

&lt;p&gt;After all, a reader might step away to the restroom immediately after opening the page, bump into a colleague in the hallway on the way back, chat for a while, and only begin scrolling the article after finally returning to their seat. If &lt;code&gt;setInterval&lt;/code&gt; continues to run for minutes, or potentially over an hour, while waiting for them, the browser cannot enter an idle state. Especially on mobile devices, this would result in &lt;strong&gt;the birth of a “mysteriously battery-draining page.”&lt;/strong&gt; We would certainly like to avoid that.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Dynamic Binding Using MutationObserver
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Detecting Videos and Preventing Double Initialization with MutationObserver
&lt;/h3&gt;

&lt;p&gt;To accommodate various lazy-loading approaches, we configure the &lt;code&gt;MutationObserver&lt;/code&gt; to monitor two types of mutation events simultaneously: the dynamic addition of DOM nodes and changes to the &lt;code&gt;src&lt;/code&gt; attribute of existing elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initMutationObserver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detectedIframes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Pattern A: Monitor the dynamic addition of DOM nodes&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;childList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEMENT_NODE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IFRAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="nx"&gt;detectedIframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="c1"&gt;// Extract nested iframes as well&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                            &lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;detectedIframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; 

            &lt;span class="c1"&gt;// Pattern B: Monitor changes to the src attribute of existing elements&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attributes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributeName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEMENT_NODE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IFRAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;detectedIframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Filter and process only YouTube iframes from the detected ones&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;youtubeIframes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;detectedIframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isYouTubeIframe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;youtubeIframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;handleNewIframes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;youtubeIframes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;subtree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;attributeFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// Restrict monitoring to the src attribute to minimize performance impact&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the implementation to instantly capture the appearance of a video, regardless of the mechanism the LazyLoad plugin uses to delay it. Specifying &lt;code&gt;attributeFilter: ['src']&lt;/code&gt; limits the attribute monitoring to &lt;code&gt;src&lt;/code&gt;, minimizing unnecessary overhead.&lt;/p&gt;

&lt;p&gt;Furthermore, since a &lt;code&gt;MutationObserver&lt;/code&gt; triggers frequently, detection events can occur multiple times for the same &lt;code&gt;iframe&lt;/code&gt;. If double initialization occurs, it would hinder not only coexistence with other plugins but also the plugin’s own processing. To reliably prevent this double processing, we assign a custom &lt;code&gt;data-&lt;/code&gt; attribute to the &lt;code&gt;iframe&lt;/code&gt; under process to manage its state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isYouTubeIframe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IFRAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Exclude elements already processed by this plugin&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-ku-sticky-processed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
           &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube-nocookie.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
           &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtu.be&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleNewIframes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Mark as processed&lt;/span&gt;
        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-ku-sticky-processed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Execute initialization and binding for this iframe&lt;/span&gt;
        &lt;span class="nf"&gt;initPlayingMode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elements with the &lt;code&gt;data-ku-sticky-processed&lt;/code&gt; attribute are immediately excluded from future &lt;code&gt;MutationObserver&lt;/code&gt; detections, reliably avoiding unnecessary loops and conflict errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamically Triggered Conflict Avoidance Process
&lt;/h3&gt;

&lt;p&gt;This is the core of the “dynamic binding” design. While the polling and random jitter introduced in the &lt;a href="https://karasunouta.com/en/web-tech/how-to-load-the-youtube-iframe-player-api-without-breaking-other-plugins/" rel="noopener noreferrer"&gt;previous article&lt;/a&gt; were triggered based on a static timing like page load, the current design using &lt;code&gt;MutationObserver&lt;/code&gt; shifts to a dynamic model: &lt;strong&gt;every time a video appears on the page, an asynchronous process starting from conflict avoidance (mutual concession) to safe binding is initiated specifically for that video.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initPlayingMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Insert the YouTube API script if it has not been loaded yet&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script[src*="youtube.com/iframe_api"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.youtube.com/iframe_api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Start polling for this video, waiting for other plugins to complete initialization&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;apiReadyAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isApiReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isApiReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;apiReadyAttempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if other plugins have already created a player instance for this iframe&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isApiReady&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Termination condition: shared target found or wait time limit exceeded&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldStop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;apiReadyAttempts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldStop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;setupCompleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;// 3. Apply random jitter (0 to 100ms delay)&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jitterDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;setupPlayers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;jitterDelay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this asynchronous design, the following scenario executes autonomously. The actors involved are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;KU Sticky Video for YouTube plugin&lt;/strong&gt;: The subject of this article. Referred to as “this plugin.”&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;LazyLoad plugin&lt;/strong&gt;: A plugin that reduces page weight by lazy-loading YouTube videos during scrolling or other actions. Examples include &lt;a href="https://wordpress.org/plugins/lazy-load-for-videos/" rel="noopener noreferrer"&gt;Lazy Load for Videos&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Other plugins dependent on the YouTube API&lt;/strong&gt;: Third-party plugins that use the YouTube API to apply various effects to YouTube videos on the page. Referred to as “other plugins.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The primary goal of this article is to make this plugin coexist with the lazy-loading plugin (No. 2). While coexistence with other plugins (No. 3) was achieved in the previous article, our objective here is to &lt;strong&gt;ensure that coexistence with other plugins remains intact even after achieving compatibility with LazyLoad plugins.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The actual execution flow is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; When a visitor scrolls down the page or performs a similar action, the &lt;strong&gt;LazyLoad plugin&lt;/strong&gt; loads the YouTube video (iframe).&lt;/li&gt;
&lt;li&gt; The &lt;code&gt;MutationObserver&lt;/code&gt; of &lt;strong&gt;this plugin&lt;/strong&gt; detects its appearance, checks for the presence of the API, and starts polling.&lt;/li&gt;
&lt;li&gt; Simultaneously, the &lt;strong&gt;other plugin&lt;/strong&gt; initiates its own initialization (player creation) process for the same iframe.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;This plugin&lt;/strong&gt; polls the initialization state of the other plugin every 100ms. If it detects that the &lt;strong&gt;other plugin&lt;/strong&gt; has created a player instance using &lt;code&gt;YT.get()&lt;/code&gt;, it safely shares events with that instance.&lt;/li&gt;
&lt;li&gt; If the &lt;strong&gt;other plugin&lt;/strong&gt; does not create a player instance, &lt;strong&gt;this plugin&lt;/strong&gt; creates its own player instance after a random jitter delay.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By keeping the process for each video completely independent, the necessary processing is executed at the exact moment the video appears, regardless of how long the reader waits before scrolling. This implementation maximizes coexistence not only with LazyLoad plugins but also with other YouTube API-dependent plugins (refer to the previous article for details on the role of random jitter).&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Coexistence Adapting to an Expanded Time Dimension
&lt;/h2&gt;

&lt;p&gt;In plugin development, conflicts with lazy loading (LazyLoad) are a common challenge. Previously, initialization order was adjusted within a tight window of a few seconds immediately after page load. LazyLoad, however, introduces an unpredictable and extended time dimension dictated by the reader’s scroll speed. The KU Sticky Video for YouTube plugin previously failed to account for this variable time frame, which was a significant oversight.&lt;/p&gt;

&lt;p&gt;One could avoid conflicts by declaring incompatibility with LazyLoad plugins and requesting users to disable them, but this compromises the user’s desire to optimize site loading speed. Fortunately, the combination of &lt;code&gt;MutationObserver&lt;/code&gt; and dynamic asynchronous polling implemented here allows the KU Sticky Video for YouTube plugin to operate stably while preserving the performance benefits of lazy-loading plugins.&lt;/p&gt;

&lt;p&gt;To achieve coexistence through &lt;strong&gt;mutual concession&lt;/strong&gt;, it is crucial to detect changes in the environment and trigger actions accordingly. I am reminded that this approach is an essential design pattern for ensuring that various third-party scripts coexist smoothly and perform as intended in the complex WordPress ecosystem.&lt;/p&gt;

&lt;p&gt;The dynamic binding design introduced in this article has already been deployed in updates for the free version (v1.9.0 and later) and the Pro version (v1.4.0 and later) of “KU Sticky Video for YouTube.” The sticky video feature should now work reliably even when lazy loading is active. If you have any feedback, please feel free to let us know via the &lt;a href="https://karasunouta.com/en/contact/" rel="noopener noreferrer"&gt;contact form&lt;/a&gt;!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://karasunouta.com/en/wp-plugins/ku-sticky-video-for-youtube/" rel="noopener noreferrer"&gt;WordPress plugin that makes the video player follow the scroll (Demo &amp;amp; Introduction)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://wordpress.org/plugins/ku-sticky-video-for-youtube/" rel="noopener noreferrer"&gt;KU Sticky Video for YouTube – WordPress plugin | WordPress.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://karasunouta.com/en/store/ku-sticky-video-for-youtube-pro/" rel="noopener noreferrer"&gt;KU Sticky Video for YouTube Pro (Premium Store)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>javascript</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Load the YouTube IFrame Player API Without Breaking Other Plugins</title>
      <dc:creator>Ryo</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:39:04 +0000</pubDate>
      <link>https://dev.to/ryo-karasunouta/how-to-load-the-youtube-iframe-player-api-without-breaking-other-plugins-204i</link>
      <guid>https://dev.to/ryo-karasunouta/how-to-load-the-youtube-iframe-player-api-without-breaking-other-plugins-204i</guid>
      <description>&lt;p&gt;When developing plugins for WordPress, one issue that almost everyone faces at least once is &lt;strong&gt;plugin conflicts&lt;/strong&gt;. The other day, I was developing a plugin (&lt;a href="https://karasunouta.com/en/wp-plugins/ku-sticky-video-for-youtube/" rel="noopener noreferrer"&gt;KU Sticky Video for YouTube&lt;/a&gt;) that makes YouTube videos in posts follow the scroll to stay in the corner of the screen. It worked perfectly in a clean local testing environment, and I was proud, thinking, “All right, it’s complete!”&lt;/p&gt;

&lt;p&gt;However, as soon as I moved the testing ground to a multi-plugin environment where another of my own YouTube-related plugins (unreleased) and other video-related plugins were active, things changed completely. The sticky feature, which had been working smoothly until just moments before, fell completely silent without moving an inch, and when I opened the browser console, there was that annoying red error text. &lt;strong&gt;Oh no.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But this is simply a common occurrence in plugin development. In reality, the solution varies depending on the cause of the conflict. In this article, I will explain the mechanism behind the “struggle for leadership” caused by the YouTube Iframe API, which many YouTube-related plugins depend on, in a multi-plugin environment, and introduce a JavaScript design to avoid this problem, share the ride with other scripts, and achieve coexistence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Conflict? The YouTube API Struggle for Leadership
&lt;/h2&gt;

&lt;p&gt;To highly control YouTube videos from JavaScript, you need to load the official &lt;a href="https://developers.google.com/youtube/iframe_api_reference" rel="noopener noreferrer"&gt;YouTube Iframe Player API&lt;/a&gt;. In typical tutorial articles, implementation code like the following is usually introduced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load the API script&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.youtube.com/iframe_api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;firstScriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Define the global function called when loading is complete&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onYouTubeIframeAPIReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;player-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onStateChange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;onPlayerStateChange&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this code seems perfectly fine at first glance, it causes issues in &lt;strong&gt;environments where multiple plugins and themes run asynchronously&lt;/strong&gt;, such as in WordPress. Put simply, what happens if another plugin defines &lt;code&gt;window.onYouTubeIframeAPIReady&lt;/code&gt; in exactly the same way? Due to the nature of JavaScript, the initialization function defined first will be completely overwritten and lost by the plugin execution that runs later.&lt;/p&gt;

&lt;p&gt;As a result, the plugin that loaded first cannot detect when the API has finished loading, and it will never start. This is exactly why the initial version of KU Sticky Video for YouTube stopped working.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approaches to a Solution: How to Avoid “Fights” Between Plugins
&lt;/h2&gt;

&lt;p&gt;To prevent this conflict and ensure safe operation in any environment, we decided to take the following countermeasures in the KU Sticky Video for YouTube plugin.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. “Script Presence Check” to Fill the Gap in Asynchronous Loading
&lt;/h3&gt;

&lt;p&gt;When the loading of the YouTube API script is complete, a &lt;code&gt;window.YT&lt;/code&gt; object is automatically created in the browser’s global scope (&lt;code&gt;window&lt;/code&gt;). This &lt;code&gt;window.YT&lt;/code&gt; object contains all the actual functions of the API for creating and controlling the YouTube player.&lt;/p&gt;

&lt;p&gt;The tricky part is that it is not as simple as, “I see. Then I just need to check if &lt;code&gt;window.YT&lt;/code&gt; exists.” If you do only that, what happens if the script runs &lt;strong&gt;“during the network load (when &lt;code&gt;window.YT&lt;/code&gt;&amp;nbsp;is undefined) right after another plugin inserted the tag”&lt;/strong&gt;? The script tag would be inserted twice. To prevent this, in addition to &lt;code&gt;window.YT&lt;/code&gt;, we also check whether a script tag for the YouTube API already exists in the DOM tree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Search by partial match, considering URL notation variants (iframe_api / player_api)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alreadyLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script[src*="youtube.com/iframe_api"], script[src*="youtube.com/player_api"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;alreadyLoading&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Insert the script tag for the first time here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs.w.org%2Fimages%2Fcore%2Femoji%2F17.0.2%2Fsvg%2F1f4a1.svg" alt="💡" width="36" height="36"&gt; &lt;strong&gt;Why continue to check window.YT as well?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;At first glance, checking only whether the script tag exists in the DOM (&lt;code&gt;alreadyLoading&lt;/code&gt;) seems sufficient. After all, &lt;code&gt;window.YT&lt;/code&gt; is supposed to be created after the script is loaded. However, there may be cases where a script tag for the YouTube API does not exist in the DOM—for instance, if another plugin bundles the API directly into its JS via build tools like Vite and loads it.&lt;/p&gt;

&lt;p&gt;Therefore, it is necessary to perform a double check using both &lt;code&gt;window.YT&lt;/code&gt; to 100% guarantee that the load is already complete, and &lt;code&gt;alreadyLoading&lt;/code&gt; to detect via the script tag that “the asynchronous load is in progress right now.” By using both together, the risk of conflicts can be minimized.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. “Event Sharing (Binding)” via window.YT.get()
&lt;/h3&gt;

&lt;p&gt;If another plugin has already created a &lt;code&gt;YT.Player&lt;/code&gt; instance for a YouTube iframe on the page, executing &lt;strong&gt;&lt;code&gt;new YT.Player&lt;/code&gt; again on top of it will cause an error&lt;/strong&gt;. Therefore, we use the YouTube API global method &lt;code&gt;window.YT.get()&lt;/code&gt; to retrieve the already-created player instance, if it exists, and “share the ride” by binding only our event listeners to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;existingPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;iframeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Get by the official spec if an ID exists. Otherwise, fall back to passing the DOM element.&lt;/span&gt;
    &lt;span class="nx"&gt;existingPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;iframeElement&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;existingPlayer&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Share the event with the existing instance if it exists&lt;/span&gt;
    &lt;span class="c1"&gt;// * Unlike standard DOM, the YouTube API's addEventListener requires the "on" prefix (onStateChange)&lt;/span&gt;
    &lt;span class="nx"&gt;existingPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onStateChange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;handlePlayerStateChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Otherwise, create a new instance&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframeElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the official argument for &lt;code&gt;YT.get()&lt;/code&gt; is the “iframe ID string,” we set up a two-step binding process using the unofficial yet highly compatible method of passing the DOM element as a fallback in case there is no ID attribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. “Polling Control” to Prevent Timing Conflicts
&lt;/h3&gt;

&lt;p&gt;The core of these countermeasures is &lt;strong&gt;timing control&lt;/strong&gt; to determine when the API load is complete and when other plugins have finished initialization. For this purpose, we decided to adopt a technique called polling.&lt;/p&gt;

&lt;p&gt;However, the YouTube API comes with a global callback function &lt;code&gt;window.onYouTubeIframeAPIReady&lt;/code&gt; that is automatically executed by the API when the script loading is complete. Why was such a roundabout method like polling necessary instead of monitoring that standard event handler?&lt;/p&gt;

&lt;h4&gt;
  
  
  3-1. The Asynchronous Risk Inherent in Wrappers for the Standard “onYouTubeIframeAPIReady”
&lt;/h4&gt;

&lt;p&gt;When considering conflicts with other plugins, a common approach introduced as a standard trick on many development blogs is to “temporarily save the existing callback function into a variable, wrap it, and execute both the other plugin’s initialization and your own in succession” as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Save the existing callback if there is one&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousOnReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onYouTubeIframeAPIReady&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Overwrite the global callback and execute the other processes in succession&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onYouTubeIframeAPIReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;previousOnReady&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;previousOnReady&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;myPluginInit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Initialize our own plugin&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this seems like a perfect solution for coexistence at first glance, this implementation actually carries a fatal risk: &lt;strong&gt;it cannot handle cases where other plugins initialize the player with asynchronous processing in between&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, suppose the other plugin we want to coexist with is designed to initialize by running an asynchronous process, such as “detecting API Ready, fetching configuration data via Ajax, and then creating the player.” In this case, our plugin’s (Sticky’s) initialization process has no way of knowing that the other plugin is waiting for its background asynchronous process to finish. Therefore, at the exact moment of API Ready, it determines that “no conflicting player instance exists yet” and goes ahead to create a new player (&lt;code&gt;new YT.Player&lt;/code&gt;) ahead of the other.&lt;/p&gt;

&lt;p&gt;By the time the other plugin finishes its asynchronous processing and tries to create its player, our plugin’s initialization is already complete. This causes a &lt;strong&gt;double-initialization error&lt;/strong&gt; for the same iframe element, causing the other plugin to fail. Therefore, if you want to write a robust implementation that accounts for conflicts, you cannot use this &lt;code&gt;onYouTubeIframeAPIReady&lt;/code&gt; callback directly as your own initialization trigger.&lt;/p&gt;

&lt;p&gt;Our plugin must not interfere at all, such as by overwriting or assigning to the global callback; it should simply load the API, monitor in the background to see “when the other plugin’s initialization is complete,” and then safely share the ride (bind) to it. This is precisely why we need time control using “polling,” which will be explained next.&lt;/p&gt;

&lt;h4&gt;
  
  
  3-2. The Solution: Controlling Initialization Timing with Polling
&lt;/h4&gt;

&lt;p&gt;To cleanly link the three approaches explained so far—wrapping, double-loading prevention, and event sharing—&lt;strong&gt;controlling the timing of the initialization process&lt;/strong&gt; is extremely important. This is where a design pattern called &lt;strong&gt;“polling”&lt;/strong&gt; comes in handy, which periodically checks the API load status and player initialization status from the program.&lt;/p&gt;

&lt;p&gt;In JavaScript, polling is mainly implemented using &lt;code&gt;setInterval&lt;/code&gt;. The actual initialization code is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initPlayingMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Load the API after checking if another script has already been inserted&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script[src*="youtube.com/iframe_api"], script[src*="youtube.com/player_api"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.youtube.com/iframe_api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;)[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstScriptTag&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Poll and monitor the API load and the completion of other plugins' initialization&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;apiReadyAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isApiReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;isApiReady&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;apiReadyAttempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if other plugins have finished creating instances for all iframes&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;isApiReady&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Determine the termination conditions&lt;/span&gt;
        &lt;span class="c1"&gt;// - Binding by other plugins is complete (allFound), or&lt;/span&gt;
        &lt;span class="c1"&gt;// - The grace period (300ms) to wait for other plugins' initialization after the API loads has passed (apiReadyAttempts &amp;gt;= 3), or&lt;/span&gt;
        &lt;span class="c1"&gt;// - The maximum timeout (3 seconds) has been reached (attempts &amp;gt;= 30)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldStop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allFound&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;apiReadyAttempts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;shouldStop&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;checkInterval&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;// Perform either "ride-sharing" or "new creation" for each iframe&lt;/span&gt;
                &lt;span class="nf"&gt;setupPlayers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the API is ready, instead of initializing the player immediately on our own, this code sets up a maximum wait time of 300ms (checking the status up to 3 times every 100ms = polling). If another plugin is attempting to asynchronously create a player at the moment of API Ready, the purpose is to wait a short while for its completion before sharing the ride, thereby preventing double-creation errors as much as possible.&lt;/p&gt;

&lt;p&gt;However, if you wait too long and our plugin’s initialization is delayed unnecessarily, there is a risk that the event listeners will not be registered yet by the time the video starts playing. As a result of trial and error balancing this trade-off, this 300ms wait time emerged as the empirical magic number (the best practical threshold) in this development process.&lt;/p&gt;

&lt;p&gt;This value might vary depending on the purpose of your plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Ultimate Question: When Seemingly Perfect Codes Meet
&lt;/h2&gt;

&lt;p&gt;With the implementation so far, we seem to have completed a mostly perfect conflict avoidance code that “does not interfere with the loading of other plugins,” “shares the ride on players created by other plugins,” and “waits 300ms to prevent double creation.” Some readers might also have agreed, thinking, “I see, this implementation looks like it should work fine.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is a question. If Plugin A and Plugin B—two separate plugins, both incorporating the “perfect implementation” explained in this article—are loaded on the same page at the same time, can they coexist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer, unfortunately, is &lt;strong&gt;“yes, there are cases, albeit rare, where they will cause conflict errors and go silent (break down).”&lt;/strong&gt; The cause lies in the “asynchronous registration lag” of the YouTube API. In fact, there is a tiny lag of a few milliseconds between when you execute &lt;code&gt;new YT.Player()&lt;/code&gt; and when that player instance is registered inside the API and becomes retrievable via &lt;code&gt;window.YT.get()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If Plugins A and B, which share the same “300ms wait” logic, start their timers at the exact same API Ready timing, both will execute &lt;code&gt;YT.get()&lt;/code&gt; almost simultaneously after 300ms. At this moment, since neither has created a player yet, the return value for both will be &lt;code&gt;null&lt;/code&gt;. Consequently, both assume that no existing player exists, and both execute &lt;code&gt;new YT.Player()&lt;/code&gt; almost at the same time. As a result, a double-creation error occurs on the same iframe, causing both to fail.&lt;/p&gt;

&lt;p&gt;You might think, “Then why not detect double creation inside the &lt;code&gt;onReady&lt;/code&gt; callback and clean it up with &lt;code&gt;destroy()&lt;/code&gt;?” However, the YouTube API’s &lt;code&gt;destroy()&lt;/code&gt; forcibly rewrites the DOM elements and clears event handlers, carrying the fatal side effect of taking down and destroying the player that was already running normally. In other words, &lt;strong&gt;once a double creation has occurred, it cannot be safely removed afterward&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing “Random Jitter” as the Solution
&lt;/h3&gt;

&lt;p&gt;Since the YouTube API does not provide events for perfect synchronization across multiple plugins, there is no perfect code that can reduce the probability of this timing collision to 0%. However, the solution to suppress this collision probability to practically zero (an ignorable level) is the introduction of &lt;strong&gt;“random jitter”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What we do is very simple. After the 300ms grace period expires, instead of creating the new player immediately, we insert a &lt;strong&gt;“random delay of 0 to 100ms” right before execution&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Executed after the 300ms monitoring termination condition is met&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;shouldStop&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;checkInterval&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setupCompleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Disperse execution timing by inserting a random jitter (delay) of 0 to 100ms&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jitterDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Check window.YT.get() once more after the random delay expires&lt;/span&gt;
            &lt;span class="nf"&gt;setupPlayers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;iframes&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;jitterDelay&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding a random delay, there is a high probability of a discrepancy in the execution timing of Plugin A and Plugin B, which started moving at the same time.&lt;/p&gt;

&lt;p&gt;For example, suppose the random delay is 15ms for Plugin A and 78ms for Plugin B. After 15ms, Plugin A, whose delay expired first, creates a &lt;code&gt;new YT.Player&lt;/code&gt;. Subsequently, when Plugin B performs its &lt;code&gt;YT.get()&lt;/code&gt; check after 78ms, the instance from Plugin A will already be detected. As a result, Plugin B cancels the creation of a new instance and safely shares the ride on Plugin A.&lt;/p&gt;

&lt;p&gt;The beauty of this design is that &lt;strong&gt;“if all plugins depending on the YouTube API adopt this ‘concession via random jitter’ design, conflict-free coexistence (ride-sharing) can be achieved autonomously in almost all cases.”&lt;/strong&gt; This is, so to speak, a piece of wisdom to achieve coexistence by writing the spirit of mutual concession into code without waiting for improvements on the API side.&lt;/p&gt;

&lt;p&gt;We have also built this collision avoidance and coexistence design using random jitter into the sticky plugin we developed, &lt;strong&gt;“KU Sticky Video for YouTube”&lt;/strong&gt; (and its paid Pro version).&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using the API of a major external service like YouTube in a plugin inevitably means harboring the potential for conflicts.&lt;/p&gt;

&lt;p&gt;Users embed YouTube videos in their posts and apply various customizations to them. For this, it is naturally expected that they will use multiple YouTube-related plugins together. In anticipation of this, designing an implementation that allows the dependent API to be used without conflicts is indispensable.&lt;/p&gt;

&lt;p&gt;The recently developed &lt;strong&gt;KU Sticky Video for YouTube&lt;/strong&gt; and its paid version, &lt;strong&gt;KU Sticky Video for YouTube Pro&lt;/strong&gt;, were also successfully released after identifying and resolving these issues one by one during development. If you want to showcase YouTube videos effectively alongside text, we highly recommend giving them a try.&lt;/p&gt;

&lt;p&gt;Please also try using them alongside other YouTube plugins! They should work without any problems (but if you do run into issues, please let me know quietly via the &lt;a href="https://karasunouta.com/en/contact/" rel="noopener noreferrer"&gt;contact form&lt;/a&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://karasunouta.com/en/wp-plugins/ku-sticky-video-for-youtube/" rel="noopener noreferrer"&gt;A WordPress plugin where the video player follows you (Demo &amp;amp; Info page)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://wordpress.org/plugins/ku-sticky-video-for-youtube/" rel="noopener noreferrer"&gt;KU Sticky Video for YouTube (WordPress.org official)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://karasunouta.com/en/store/ku-sticky-video-for-youtube-pro/" rel="noopener noreferrer"&gt;KU Sticky Video for YouTube Pro (Paid version store)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>javascript</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
