Streaming live at 10am (PST)

High performance, native-styled YouTube Embed

The default YouTube embed in Webflow is really slow and heavy for mobile page loading performance, especially for light websites targeting low-bandwidth / low-CPU audiences. The YouTube embed alone adds 10+ requests, hundreds of KBs, and for light websites, it’s even noticeable on a wired 100Mbps ISP / i5-8600K system / 32 GB system.

Here’s a much more performant version that looks just like a native YouTube embed. All credit goes to groupboard for their ytdefer repo.

Check out the demo here (non-Webflow):

This embed’s improvements over Webflow’s native YouTube embed:

  • loads extremely quickly (a 4 KB script)
  • looks and feels like the native YouTube player (embedded SVGs!)
  • only one click to begin the video, unlike almost every (all?) YouTube defer scripts
  • allows custom thumbnails (say like an SVG you’ve added to the Webflow Asset Manager)

Before (using Webflow’s default YouTube embed):

After (using groupboard’s ytdefer):

And folks, that’s just one video embed. Unfortunately, this is for a private project so I can’t share the Webflow URLs, but you’ll be done in 5 minutes, if not less.


A) Add the 4 KB script!
B) Add the div!
C) Add custom thumbnail! (optional)

  1. Add the 4 KB script! In Webflow, open your page’s settings (the page where the video will go). Scroll down to before /body tag at the end. Copy the script (this is the latest minified version) & copy this Javascript call to attach at the end on a new line:
window.addEventListener('load', ytdefer_setup);

Type <script>, paste in both the minified version & the JavaScript call on a new line after it, and type </script> to close the script. It should look like this when you’re done:

var ytdefer_ic_w=73;var ytdefer_ic_h=52;var yt_icon='<svg height="100%" version="1.1" xmlns="" xmlns:xlink="" viewBox="0 0 68 48" width="100%"><path class="ytp-large-play-button-bg" d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#eb3223"></path><path d="M 45,24 27,14 27,34" fill="#fff"></path></svg>';var yt_dark_icon='<svg height="100%" version="1.1" xmlns="" xmlns:xlink="" viewBox="0 0 68 48" width="100%"><path class="ytp-large-play-button-bg" d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#212121" fill-opacity="0.8"></path><path d="M 45,24 27,14 27,34" fill="#fff"></path></svg>';function ytdefer_setup()
{var d=document;var els=d.getElementsByClassName('ytdefer');for(var i=0;i<els.length;i++)
{var e=els[i];var ds=e.getAttribute('data-src');if(!ds)
{alert("data-src missing for video");return;}
var w=e.clientWidth;var h=e.clientHeight;var dv=d.createElement('div');'ytdefer_vid'+i;'px';'px';'relative';dv.onresize=ytdefer_resize;e.appendChild(dv);var im=d.createElement('img');var res='0';if(w>480)
im.src=''+ds+'/'+res+'.jpg';'ytdefer_img'+i;'100%';'100%';'cover';'absolute';im.onclick=gen_ytdefer_clk(i);dv.appendChild(im);var bt=d.createElement('button');"url(data:image/svg+xml;base64,"+window.btoa(yt_dark_icon)+")";'ytdefer_icon'+i;'absolute';'0';'transparent';'px';'px';'px';'px';'none';dv.appendChild(bt);im.onmouseover=gen_mouseover(bt);im.onmouseout=gen_mouseout(bt);}
{var js=d.createElement("script");js.type="text/javascript";js.src="";d.body.appendChild(js);}
function ytdefer_resize()
{var d=document;var els=d.getElementsByClassName('ytdefer');for(var i=0;i<els.length;i++)
{var e=els[i];var w=e.clientWidth;var h=e.clientHeight;var dv=d.getElementById('ytdefer_vid'+i);'px';'px';var ic=d.getElementById('ytdefer_icon'+i);if(null!=ic)
function gen_mouseout(bt)
{return function()
function gen_mouseover(bt)
{return function()
function gen_ytdefer_clk(i)
{return function()
{var d=document;var el=d.getElementById('ytdefer_vid'+i);var vid_id=el.parentNode.getAttribute('data-src');var player=new YT.Player(,{,,videoId:vid_id,events:{'onReady':function(ev){}}});}}

    window.addEventListener('load', ytdefer_setup);
  1. Make the video div! Create a new div: this is where your video will appear. First, give it the class ytdefer

Then head over to Element Settings. Add a custom attribute data-src + the YouTube video ID:

The YouTube video ID is the string of letters/numbers at the end after watch?v=

For this video, use this: dQw4w9WgXcQ


You’re done! Congrats. Enjoy the much faster page loading performance, especially on mobile!


You’ll notice the script calls the default YouTube thumbnail, which is great for most videos and reduces manual labor. YouTube has excellent CDNs, strong compression, and any time you switch the thumbnail on the video in’s settings, it’ll automatically switch on your published Webflow site.

But, let’s say your logo is text-heavy and you just happen to upload a 2 KB SVG to your Webflow Asset Manager and the client won’t be changing the thumbnail later (remember to embed fonts! I love for this!). If you could use that SVG, you’d 1) have one less external URL request, 2) have higher quality image at any breakpoint, and 3) also use less bandwidth than the JPG.

The solution: just replace this image URL found in the JavaScript we copied earlier with your own Webflow asset’s URL and, wam, bam, you’ve got a very fast, native-looking YouTube embed.


(keep the quotes. it should be .svg' at the end, not .svg – use the extension of your image).


One thing you’ll realize: you’ll only get the default YouTube styling, with the bright red seek bar and the related videos at the end could be from any which channel. To get a white seek bar & related videos only from the same channel, you’ll need to use YouTube Parameters. Add this line to the code almost near the end of the script at this location (if it’s minified, just look carefully and you can remove the spaces):


Note the syntax: words use single-quotes like 'color' and 'white' while numbers are plain like 0.

This approach is needed because we cannot use the &rel=0 or &color=white URL parameters because this script uses YouTube’s Player API instead of the typical (and slow) iframe approach. The parameters are the same, but the syntax is different and they go inside the script itself, not behind the URL.

If you want more parameters or want to do wilder things:

See a list of all current YouTube parameters
See the YouTube Player API reference (CTRL/CMD+F for playerVars)


Hello @TG2,

Thanks for sharing. I’ll give it a try on a future project.

1 Like

Hello, @Pablo_Cortes!

Cheers. I’d be excited to see your results!

Updated the guide (23/10/19) to teach how to use the customize YouTube parameters, so you can get rid of the bright red seek bar and kill all the “suggested videos” from every corner of YouTube -> only suggest videos from the same channel.

It’s crazy easy and depending on your website’s theme, a white seek bar looks much better than the default red one.

Since the script uses the native YouTube Player API, we’ll add the parameters via playerVars. Then you can add any parameter you’d like (though YouTube is wont to remove/kill parameters every few months; the latest list is linked above).

Hey @TG2,

Great post!!

I’m trying to implement this into my project, but running into an issue where the div block isn’t showing a thumbnail on the published page.

I see the “play button”, but no thumbnail.

I’ve tried the default code, and the custom code using a thumbnail image URL from my assets in my project. But either way, no thumbnail.

Curious if you’d be able to take a look?

Hi! Thank you. Glad I could share. I’ve got a few more tweaks to add soon (one I may submit to the script author: native lazy-loading via browsers is coming!).

Oh, yes: I think I see your issue. I should’ve added this in the main post because Webflow’s YouTube embed does this automatically! My apologies.

The issue is that the ytdefer div there has no height. In Webflow & HTML, div’s with auto height collapse to zero height. Unfortunately. You can test this with other divs, too. auto doesn’t do what it sounds like it should do, which is annoying. This is also why on the mobile landscape (width: 100%) and mobile portrait (width: auto) breakpoints, the div collapsed to a single point. One of the more frustrating parts of CSS.

So, not exactly a script issue, but more HTML/CSS issue. Here are the two common solutions; I use the 2nd more “modern” method because it works everywhere & saves time (and it’s what Webflow and 99% of responsive YouTube embeds do).

  1. You can set a defined height per breakpoint on the ytdefer div. You’ve set the video to 720px width, so here on the desktop breakpoint, just add 405px height, assuming it’s a 16:9 ratio video.

  2. You can use the responsive “intrinsic ratio” tip so the video scales automatically. This is how Webflow does it for their native YouTube embed, IIRC, and how 99% of all responsive YouTube video embeds are created. Assuming a 16:9 video, create a div called something like ytdefer-wrapper. Give it these settings in Webflow:

    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;

Example (my div is called PC-Video-Wrapper = ytdefer-wrapper)

Then, on your ytdefer div, (with the data-src attribute) set it like so:

    position: absolute;
    width: 100%;
    height: 100%;

Example (my div is called ytdefer-new = ytdefer):

Then, put the ytdefer div inside this ytdefer-wrapper div. Now, the thumbnail should show up & you’ll see the script working.

The play button shows up because of its absolute positioning, but probably does look very weird without the thumbnail, heh.

Source & more about responsive embeds here: (this shows CSS’ old age quite well)

1 Like


I’ve gone with solution 2 that you provided.

I see the thumbnail, finally :slightly_smiling_face:

However, the thumbnail is now showing only half of itself.


Also, on mobile the thumbnail is still missing :frowning:

1 Like

Ah, OK. Let’s give this a shot; I can’t test the thumbnail from the Designer, unfortunately.


width: 100%
height: 100%
top: 0
left: 0

You’ve got ytdefer position currently as 0% on top, left, right, and bottom. Changing to top & left to zero, that should get the thumbnail correctly set.

The problem on mobile: oh, I see. Now this is more a CSS layout issue and not from the script. The script is “dumb”, in that it’ll fit whatever CSS you have—which is the same as Webflow’s YouTube component. You can test this out by putting Webflow’s own YouTube component where your ytdefer div is: it’ll have the same issue, at least in the Designer.

The fix: the parent div Hero Container switches to flex on mobile. You can either switch that back to block, as you have it on desktop/tablet, or keep it flex and change align to stretch instead of center, as you have it now. Then the video should appear as the div is large enough.

A neat tip: the div itself should be visible in Webflow as you want it to appear. The script more or less should fill whatever size the ytdefer div has. So, if the div isn’t visible or correctly sized in Webflow, the div and its video won’t be visible or correctly sized on the site. That way, you can troubleshoot within Webflow and play with the settings to make sure the div looks how you want.


I REALLY appreciate your help with this, I really do.

For whatever reason, the issue is still happening.

I’m starting to think there’s something in the code, because I’ve been making small edits to the wrapper and the div for the video to troubleshoot, but still getting the same error.

It seems that if I adjust the size > width to something like 75%, or say 50%, that it changed both the position of the thumbnail and play button, and what part of the thumbnail is visible.

Changing the settings back to your instructions for the “ytdefer” div sets everything back to “default”, which included the thumbnail only being visible 50% from the right with the play button being fully visible in the center.

It’s almost like the YouTube thumbnail file is being cropped somehow.

No worries; I’m happy to help. If we can figure this out, it’ll help somebody down the road.

It is weird as I cannot reproduce it on my site. I might suggest to try a minimal environment (new page or new project) and see if it’s still happening there. New page, only that embed, and the default code. Or,, too.

What’s weird, though, is that I did have a cropping issue once before, but that was because I had renamed the div to ytdefer2 or something and forgot to change the class names in the script’s resize function. But yours looks good there…

Have you tried setting an explicit height/width, solution 1, as a test? Or maybe another video, just to be sure?

I’ll take another look at this tomorrow: maybe with clearer eyes, we’ll find something.

1 Like


Here’s some new info!

If I change the ytdefer div size to 540px wide, 304px height, then the ENTIRE thumbnail shows up!

Here’s a screenshot:

But as you can see, it’s not centered, and the PLAY button (svg button in the script) is shifted over to the left.

1 Like

I changed ytdefer back to 100%width & 100%height.

The actual video player was 540X304px and wasn’t filling up the entire wrapper div.

Looking forward to cracking this tomorrow.

I’ll test on a diff project tonight to troubleshoot.

Thanks again for your help!

1 Like


I tried testing with a new project, but it requires hosting - so it isn’t an option.

As a workaround, I tried testing on a new blank page:
body > section > container > ytdefer-wrapper > ytdefer

Then added the code to the "before " section.

I used “copied” assets from my actual homepage where I’m having the issue. I also started from scratch and manually started from the top of your instructions.

Unfortunately, the thumbnail is still not working correctly.

I think it’s in the actual custom code, and not anything to do with Webflow.

Could be the positioning of the SVG play button image that’s found in the custom code.

I can’t really decipher how to change the position for testing, but I suppose I’ll just start tinkering and see what happens.

Ah, good point: I’m also at the limit of hosting, unfortunately.

OK, I ran a test: the script seems to be working great with your video, using iOQ2xIV5w9g. The script is doing what it should be doing.

The thumbnail’s CSS is set with parameters at line 52 of the JavaScript, unminified, found here. If you play with those in F12 Dev Tools on your live site, you may be able to figure out what needs to be changed in the JavaScript. That is moving a little beyond my expertise.

I’m honestly confused why my custom images worked, but yours aren’t sizing up properly. All of mine are SVG, which are treated more like HTML elements than raster images; thus they can be resized pretty fluidly (i.e., they don’t really have a defined height/width).

Though, to be real, the “custom image” switch isn’t something supported by the script. :frowning:


I solved it!!!

So, I had this div higher up in the hierarchy, and it had Flex enabled.

I took a hard look at my structure, and decided it wasn’t necessary, so I removed it.

Once I did that, I started over with your custom solution, right down to the custom thumbnail (using a .jpg), and your extra “ytdefer-wrapper” div.

Everything works as designed!

WHEW! I can’t believe the div with Flex 5 levels up had a direct effect on how the thumbnail renders.

1 Like

Is there a way, to include/use it in react?

This is so cool! Wow. Thanks so much. Could I add autoplay, mute and loop to this? I wouldn’t have any clue as to how.

Is there a way, to include/use it in react?

I can’t comment here, unfortunately, as I’ve only used this with Webflow. :frowning: My coding expertise unfortunately runs out of runway at this point.

This is so cool! Wow. Thanks so much. Could I add autoplay, mute and loop to this? I wouldn’t have any clue as to how.

Cheers. For autoplay and loop, yes. See “Bonus Customization Tip (Advanced)” in the OP. Specifically, the playerVars allow for autoplay and loop. You’ll add these to the script’s code, so my quick guess is something like


Mute seems to be available, but it’s no longer in the documents. I’ve unfortunately never tried these, so you may need to experiment and see what works.

Thanks for the answer! I used the playerVars but autoplay doesn’t seem to work. it’s the only one that doesn’t :confused: