 Hello, hello, hello. Actually, the late Paul Lewis is now in the cloud doing machine learning. Poor one out for Paul Lewis. So here I am. Hello, Copenhagen. So I saw this bit recently, and I really wanted to try it on stage. So Bill Bailey did it. And what he did is he made the audience clap, but not clap like in applause, but only exactly once. So like, put your hands together, exactly once, in sync with this. Are you ready? What an amazing sound. Nailed it. OK. Enough time wasted. So, hey, welcome. This is supercharged, which we usually do. Sermacharged. Sermacharged. I'm rebranding, because as you know, this person is not the bald Paul Lewis. I'm wearing a hat, though. This is the boldest cap that I could find. Close enough? Maybe? Colour colors. Monica luckily agreed to assist me today. And we're going to do the supercharged Life, Life, Life, as it is now, I guess, tradition. But Paul did move on to DeepMind, and is now working on basically on Google Home and has been working on the next edition, which is going to be the Google Home Developer Edition, or as its code named, the Chrome Home. And so we have a developer preview that we can hopefully show off a little bit today while we do our code bits. So it should be work pretty similarly as it usually does. And yeah, just. Do you want me to do it, because your accent's kind of funny? Yes, please. Hey, Chrome Home, what's the weather in Copenhagen? Bleep. Wait, really, I'm programmed with the knowledge of 50 Chrome developers. I know all about web APIs and standards. And you're going to ask me the same stupid shit you'd ask a regular Google Home? Yes. OK. The weather in Copenhagen is kind of nice. For further details, please consult your nearest window. We're going to maybe make use of that later? We'll see. It doesn't seem that helpful. Might as well have a third of a little later. I guess Louis has been spending his time very well. Let's do the thing we're actually here for. What is it? We're going to build something. Sweet, what is it? We are going to build custom elements, because after all this is the Polymer Summit, so I thought we should at least be using custom elements. Although I'm actually not using Polymer. Well, can't want it every time. Because we were focusing on the other thing that you said was use the platforms. We're going to show you the low level stuff. And I thought something that I have been encountering or noticing lately is that a lot of sites have, or blogs especially, have images like these. I have some images of Copenhagen on this test site here. And they load the images right away. Like you load the page and all the images are loaded. And that is actually pretty hurtful, because when you look at it, you have like 1.4 megabytes for opening a web page. And maybe you just want to read the first paragraph and then leave. And that's not cool. So I thought you could build like a lazy loading kind of custom element. I'm lazy. I'm into it. I'm going to stop you right there, Serma. So I would like this. Paul Lewis told me not to ruin his legacy, so we have to do some housekeeping here. We would love questions from you so that as Serma is banging on the keyboard, we can actually answer your questions, because who knows what he's going to do. And because we don't have comments on the YouTube stream, please tweet with the hashtag supercharged. I will also be looking at Polymer Summit, but I assume you're just going to be tweeting about how awesome we are on Polymer Summit. So hashtag supercharged. Ask all of your questions, and I will answer them. Or the Google Home. Or forward them to me to distract me. You know the usual deal. If you've seen this before, you know how this works. But we don't have the YouTube chat this time, so we are using the Twitter's, which also, not Waldorf, does Serma. You should totally follow us. It's totally worth your time. But don't tweet at us, because I'm not reading my Twitter. Just Polymer Summit is supercharged. That's all I got. All right, Serms. All right, let's go. Do it. All right, so what I have here is a pretty empty website, but we have four images that I totally took myself in Copenhagen. And we're going to try to make them lazy load. And just to show you what's going on, it is super vanilla. It's literally four image tags with four spacer diffs in between. And the styles are just, the spacer is just a very high diff so that there's some space in between. That is literally all we got and everything else. We're going to write here, write in our lives. You can actually watch and ask questions and stop me if I'm being stupid. So instead of using the image tag, we are now going to turn this into our SC image, because branding is important. It's our supercharged image tag. And to use those, we actually have to, of course, define the elements. We're going to include a new thing, which is called SCImageJS. Bigger text, bigger. Yeah? Like this? All right. SCImageJS. And now the usual dance, where we go, OK, SCImage, Extents, HTMLElement, Constructor, Super. There we go. Wait, but your constructor is not doing anything. Why is it there? It's just have an element at first. So now the images are gone, because the SCImage element is now in use, and we haven't done anything. So if we look at our thing, HTMLElement, this should be saying SCImage. And that is because I didn't actually call custom elements defined. So my class will just exist, and it wasn't used. So I'm going to go with custom elements define SCImage. SCImage, I think. That's how it works. Yeah. You can still see anything, but now it says SCImage. Now we are actually having our own custom elements in place, and now we can start working with them. So the first thing I want to do is make custom elements by default our inline, display inline. And for images, we don't really want them to be blocks or nice and wide and fill the space. So we're going to use Shadow DOM to give this element some inherent styles. You should make the text even bigger. Even bigger. Also, somebody asked, he is using VS Code, I believe. It is VS Code. So let's create a template, and the template gets some in-ihtml, and we're using some template tags. And in here, we have the style. And the reason I'm using a template is because instead of, in every constructor call, I could just be setting in-ihtml, but that always starts the parser, which I don't really want. So I'm going to use a template, which is much quicker to instantiate. So in here, I'm going to say display block, because our element is supposed to be display block. In our constructor, I'm going to say a Ted Shadow mode open. Can I answer your question from the livestream? So he didn't actually need to write the constructor. He wasn't going to add anything, but I think he knew ahead of time he was going to add other things. You're only calling super. You don't need to define the constructor there. I do it anyway. It's just like muscle memory. Just like, because sometimes you don't want to add stuff. So now we can do a template, content, clone node, node true. Wow. This is a family show. So let's close the console. And hopefully, we should say display block, so our elements are not display block. That was our demo. Thank you for coming. We're done here. That's it. Not quite. So let's see how we can load the image. Again, what I wanted to do is to load them when they come into view. And there's a new primitive on the web, which is called intersection observer, which allows you to, why don't you explain it? Intersection observer is a thingamajig, so that when you scroll a thingamajig into the view, the intersection observer says, hey, that thingamajig is into the view. You should do something about it. That was super concise. It's really useful if you have a giant block of text and an image at the bottom, and you really don't want to load that image until that image is actually in the view, because maybe it's never going to get in the view. So when it comes into the view, you're like, bam, show that thing. All right. Hey, I've got a question for you. All right. Why is your template outside of the class? Because I don't want to recreate the template for every instance. It's just there for me to reuse. So it's going to be parsed once, and I can re-instantiate it every time when a new element is being created, which is super fast. I mean, unless you're creating like a million images, this is actually going to make a difference. Most of them it won't, but this is just a good pattern to adopt so you don't run into these kind of problems. OK, intersection observer. Let's talk about these. You create an intersection observer, and you give the intersection observer a callback. And this callback is going to be called every time some of your elements change their state, and their state meaning being inside the viewport or outside the viewport. When they intersect with the viewport. Hey, Krumhum, how do you say intersect with the viewport in German? What? Sorry, the SIRMA module has not yet been installed. Google it. Intersector does a viewport in Civil Play? Did you mean help my nebada von nebrenz? Yeah, yeah, that's what I meant. Well, that means help my bathtub is on fire. Sorry, Google Play Services has stopped. God, it's not working really well. I'm not impressed with Paul's work recently. It's been gone for like two months, what it's been doing. It's downhill. So the callback gets a number of... It's a callback that, as a parameter, gets a number of entries. And each entry is for different elements and how the state changed. So we're going to go through all these entries, and if that entry is intersecting, meaning it is currently inside the viewport, on the element, which is the entry.target, we're going to set an attribute, which is going to be called full, which is meaning it should now show the full version of the image. And that is pretty much all we're going to use in Intersection Observer 4. Whenever an element scrolls in the viewport, the attribute full is going to get set, and then we can react to that change with our standard observed attributes that we know from the custom elements. So for that, we need our static getObserved... observed attributes, which is going to be the full attribute only. And since we only have that one observed attribute, our attribute change callback doesn't need any parameters because we know it's just going to be the full attribute. And what we're going to do is, if it's already full, we're going to return because we're not going to load the image twice, once is enough. And otherwise, we're going to create a new element, create element image tag. Image source is going to be this, the source, and now I just forgot that I should get some getters. So we are using this dot full. While you're doing the getters, lovely question from the audience, which browser support Intersection Observer, and since I'm too lazy to Google it, hey, Chrome home, which browser support Intersection Observer? It's Edge 15, Firefox 55, Chrome 58, Opera 46, and Samsung Internet 5. That's actually pretty decent. So it's something, there is a polyfill, I think there is a good polyfill for it. That you wrote? What? Didn't you write it? Somebody else wrote it. I did the first version, then I pass it on to other people who are much smarter than me. Nice. But you don't need it, apparently, because that was actually a pretty decent support list. So most of the time you'll be running without it. So you're doing two getters and setters for source and for full? I'm not doing setters because I'm not gonna... Don't set anything? Yeah, pretty much. All right, so we're creating a new image, we're copying the source from our image to the actual image tag, and then... And the reason why we're doing that is because the moment you set a source on an image, it's gonna start loading. You can't stop it from loading on that image, no matter how hard you try. Exactly, and now we're gonna wait for it to finish loading. Train, don't stop in that platform. And once it's loaded, we are gonna shadow root, attach it to the image. That is actually not... That's not right. That's a pen child, right? Next. This looks pretty okay, I think. Let's give it a try. I'm sorry, did something wrong? Where's my console? Nothing is happening, which makes sense because I just created the Intersection Observer, I didn't use it anywhere, which, you know, might be helpful. So on connected callback, this is the second part of the API. So the Intersection Observer you create, you pass a callback in to know which code to actually when something changes, and then you have to tell the Intersection Observer what to actually observe. So I'm gonna call observe this because you're gonna observe the element itself. And because we are good citizens of the web, we are also gonna do our disconnected callback and call unobserved. Nice. So now we're getting errors on line 13, which is totally what I expected. It's, oh, set attribute. I always just like this. I only wanna set the attribute, and yet I still have to say set it to an empty value. Or true, yeah. Or true, I guess. Whatever you want. But it just seems unnecessary. So let's do this. Backwards compellingly for you. I guess nothing is happening. Oh, because we are setting full and then we're checking a full, which we just set, and therefore this is not smart, so I'm gonna just call it loaded. So we're gonna have two attributes, two properties now, the full attribute and the loaded. Full is when it's supposed to be on screen and loaded when it actually is loaded and on screen. So in this callback, when it's loaded, we're actually gonna call this loaded is true. I'm not gonna define, even define an attribute because this is live and it's gonna work anywhere, right? So let's see. Cool. I mean, it's a little bit big, so that's not, but it's, the image is in here in the shadow DOM, so that's good. To be fair, he didn't set any styles, so it's gonna be as big as it is. Yeah, let's change that, shall we? So our image in here, and this is what I love about the shadow DOM, I don't have to do like complex selectors because it is scoped by the shadow DOM anyway, so I can just go image and say, with 100% and be done with it, boom. And now the thing is, if you go to the network panel, only image A has been loaded with 265 kilobytes. Once I scroll down, yes, the second one loads. Scroll down, that one. And this is, you know, in terms of data conservation for the user, this is much better because now they only actually download the data they actually have on screen. So does the IntersectionObserver run on every pixel scroll? How does it actually work? Is it performant at least? It is super performant. So I think, as far as I know, if you don't use the polyfill but have a native implementation of IntersectionObserver, it hooks into the actual layout engine. So the browser is obviously the only entity that knows if something is on screen or not. And once it is on screen, it chews up one of these callback invocations. And those are dispatched in idle time. And that means you will only get to process these entries if the browser has time to do so. So if you're busy encoding a GIF or whatever people do these days on the main thread, your IntersectionObserver callback will be delayed until there is actually a headroom to do this kind of... So you're not blocking layout? You're not blocking paint? You're not blocking your animations? Or scroll? It's great. So it can basically only get better because the most important thing really should be to be interactive for your user. Cool. All right. I'm gonna answer some more questions. Is that okay? Is that how we do it? Is that... You're doing pretty good, actually. So one of the questions is, if the image content is the Shadow DOM, can robots access it? So like these search bots, and they can if they run JavaScript. So this is the same question for SEO. Yeah, pretty much. And that you saw... What does SEO stand for? Search Engine Optimization? It served my Engine Optimization. Served Engine Optimization. I like it. Perfect. Yeah, so as you saw, exactly the same answers in the SEO talk are from here. If you're running JavaScript, it will be accessible in there. And also you should probably set some alt. We're not doing this because it's not live. It's not production. I should be setting alt. But that image doesn't have an alt. Rob Dodson is probably in the audience and not impressed. He's gonna punch me. Yeah. Please don't punch us. Because I was honest how to... I'm honest how to team and I didn't do the accessible bit. Yeah. I'm actually bad. Okay. I mean, that is pretty cool. This is working. We could say we're done. But there's something else. If you start on the... If you scroll down and read out the page and scroll up, the image is kind of popping to existence because at the start our image has no height. And then once the zero height diff comes into view, the image suddenly it has a height so they kind of appear, which is really sad. So what I would like to do, and this is something where this image is gonna be better than the native image element, it's gonna have support for aspect ratios. So we're gonna... I mean, you can do it with native elements. You're like preemptive three questions from Twitter. You're nailing it. Bam. Impact. All right. So what we're gonna do is we're gonna write like a tiny bit of a backend. So I'm gonna bring the sidebar back real quick to create a new file, which is gonna be... No, not in here. Down here. There. I'm gonna write a little backend and we're gonna be using some express because whenever I do nodes, I just use express because it's easy. And we're gonna kill our Python web server and instead start our new server. Are you gonna do some server side rendering, you would say? It's gonna be server side rendering. Yes. I'm here all day. So we create a new app and that app... Hang on. That app uses the express static middleware because mostly we're just gonna do static page delivery and app.listen on 8080. And so now everything should be working the same. Cool. It's still loading. Now we're using our new backend. And now we're gonna do something new because we're gonna define our own route for HTML files. Request, response. And what I wanna do in here is basically inspect our images to figure out what their aspect ratio is and do some CSS hackery to give the elements an aspect ratio so that they retain their aspect ratio even if the image data has not been loaded yet. And to do that, we have to first figure out which file is actually being loaded. So let's do the file path, which is request URL. And if the file path ends with a slash, that usually means that we have to add index.html, right? Because if it was a slash, it's a folder and that kind of deal. And now we have to read the file, basically. And to do that, you have to do the FS module. I'm on node eight, so I can use all the new shiny stuff. So I can use the new promiseify function from the utils module to turn the old callback version of file system into a promise version. And we all like promises, so I'm gonna do that. Hey, Google Home, do you like promises? Oh, Chrome Home, sorry, do you like promises? Yeah, I prefer streams, though. Nerd. All right, so it's pretty simple. We just pass in a function that has the standard node callback layout where the last parameter is a callback with error and result and it turns it into something that now is a function that returns a promise. And that means that we turn this whole thing into an async function and can now do const. So if you're gonna read the buffer, we can do await, read file, file path. This is gonna work because we have to add static. Then we can turn it into a string so we can send it back and then we can send these contents back. Let's hope that works. Still working, cool. So now we can read files the way which I think is much nicer to read than having either promises, all callbacks, honestly, async away really makes this code much easier. All right, we have the buffer, we have the content. And now we're gonna do some post processing on it because we need to figure out which images are being loaded, load all these images and then figure out what the aspect ratio is. So we are gonna do because, and I know you're gonna love this, we're gonna do some regex magic. Oh, God. So, but some people don't know, split- For the record, I haven't seen this code before, so I'm getting like anxiety every time he says these words. Like we're gonna do some regex magic. Do you wanna write the regex? No. No, okay. Can I answer some questions though while you type your regex? Go for it. Because nobody needs to know what you're doing. I'm gonna explain the regex, so go for the questions and then I'm gonna do the regex. Why aren't we extending the image element with isEquals? Because that's not a thing. That is not a thing, unfortunately. So isEquals is one of the battles we lost a little bit for custom elements. I mean, it's in the spec. It's in the spec, but it's not actually implemented everywhere. Not even Chrome, I think, has it for the new one spec. So it doesn't actually work anywhere. The polyfills don't have it, so you can use isEquals all you want. It's not gonna do anything. Some browsers have expressed very strong dislike of the isPattern and if one browser doesn't do it, there's no point so far at least in just doing it in some browsers because it's also very hard to polyfill. I don't even think it's polyfillable at all. Not sure about that, but for now we have to live without subtyping native elements. We can only do HTML elements and nothing else. Mm-hmm, carry on. So what I'm gonna do, we're gonna write a regex and we're gonna find all the scImage elements and we're gonna do this. So we wanna have everything until the closing tag. Come on, let's see image. It's beautiful, isn't it? And let's keep it this way and let's call join down here and let's work on the source for a bit so we can see what is going on. This means it is not working. So that's good. We're splitting this. That is correct. Thank you very much. And my images disappeared, which is actually true because I need to put parentheses around this. So now this should look the same. The good part is that now new content is an array and it's either gonna be remainder code or it's gonna be just one isolated scImage element. I can just to show what I'm talking about, I'm gonna console log new content for a bit, gonna refresh and then in the console, it's an array and every second element now is an scImage element because that is the part that matches the regex and everything that doesn't match is gonna be put in another element. So we now just split apart our entire document into what is an scImage and what is not an scImage. And now we can do post processing on that. So what we're gonna do next is we're gonna remove the semicolon and we're gonna map. And each of these items is if the item starts with scImage, actually when it doesn't start with scImage, don't wanna do anything because we don't care about it, so we're just gonna return it. And otherwise, we want to figure out what the actual source attribute is. So again, we're gonna do regex because... Oh my goodness. That's how I roll. There's parentheses around this and that and then we're gonna do an exec on the item. And then we're gonna return something. Let me think for a bit. Let me just do a test. I'm gonna return the source just to see it works. So now we know the source actually has only the value of the source attribute. So that's good. It's probably gonna read that file. And now we're gonna do the item. Actually, no. The item can actually stay. What we wanna do now next is we wanna actually figure out what the aspect ratio of the image is. And this is where it gets... We go into a little bit of the weeds of the node ecosystem because now we have to look into image processing libraries. I just Googled a bit and took the first one I found which is called GraphicsMagic for short gm and we can subclass it to use image magic because that's the only one I've installed on my system. Don't worry too much about it. Basically all the image processing libraries can do what I want, resize images and figure out what the size of an image is. And what I'm gonna do down here, I'm gonna load the image. And that's fairly easy with this library. So I'm just gonna do static plus source. And let's just do a console log to see if that worked. I hope it will. So this should all look the same. Looking good. We have loaded an image. Size function. Now this is where things are a little bit weird because the library, as all node libraries, are a little bit old and have callbacks. But the cool thing is, promissify the function I loaded from node eight actually works on libraries as well. As long as you've conformed to the standard callback pattern that node has, this is gonna work. So I'm gonna call image.size bind image. I pass on a function. It's gonna turn that function into a promise version. And then I can do with height await sizefunk. Why are you doing all this awaiting? Why don't you do it sync? Because the library is not synced. That's how callbacks work. So this is gonna. If you would have done it sync, it would have been fine. I'm using await inside the map callback which is not an async function. So I'm just gonna make it an async function. And for that to work, I also have to do a promise.all because now all the array elements are gonna be promise values. There we go. This should work again. Cool. Cool. So we see we have four image on our page and we have four widths in heights. Magic. That's why it's called image magic. Because you just do some code invocation at some point you get what you actually want. Now we have to... Now we talk about something that I really like. The... Animations? Are you gonna do some animations? No, not yet. I'm gonna talk about the aspect ratio hack in CSS. Ooh, we can ask Google. We can ask. Go for it. Maybe I'll understand you. Accent. Down. Hey Chrome home. Bleep. How does the aspect ratio hack in CSS work? Sermar, I hate you. So you have an element and it has another element or pseudo element inside it that has a padding top that is the aspect ratio that you want and then within inside it you can use absolute positioning to keep something the same size. Now everybody totally got that. That was well explained. I'm hoping the code will now actually show how this works. So the weird thing is when you define a padding top a percent in percentages. So padding top 50%. That 50% is not the height but the width of the parent element. Don't ask me why, but that's how it works. And the cool thing about that is that we can say here, actually I should be using a temp attack that this we can abuse this basically to define an aspect ratio because what we're gonna do is we're gonna do height divided by width times 100 in percent. And that means the wider the image is it will grow in height as well because padding top is proportional to the width not of the height. This hack is also really good for iframes whenever you're loading like a YouTube video that you're importing and it's always a weird aspect ratio. Do this for everything. So just to show that this is actually working I'm replacing the closing characters of the elements to inject some styles which are actually... The closing. Yes, like this. Let's take a look at this. Okay, so you can see we have injected percentages successfully. So let's look at the actual visual version. Not quite what I was going for but we can probably fix that with some styles. So we're gonna do position relative. And so the problem right now is that we have a padding on these elements and the contents of the child are being pushed down by the padding which is not what we want. So we're gonna just absolutely position the shadow DOM image inside at the top and at the left so it doesn't really care. What? Oh, I think it rolled so fast. There we go. And because we can't really see it I'm gonna slow down the network which... Where is it? Is it down here somewhere? There it is, network conditions. Let's do it on slow, fast 3G. I think it should be good enough. So you can see the rectangle is there even though the image is not loaded and once it's there it just replaces the right rectangle which is still underneath there technically but now we have images that consume the space the image will need once it's loaded. And that's something the native image tag doesn't do. It does not. I mean you can use the same tag on the native image element you can just define a padding top but I thought that this was a really neat trick to show off. And this is how you don't have like your stuff just jumping around whenever your images come in. That is the best part. If someone loads the page at the bottom for some reason and scrolls upwards stuff is not gonna jump around because the images already allocate the space that they need. The only thing I dislike about this is that they kind of like the red squares are really annoying. I'm gonna steal a question though first. The question was what does the host selector does? Oh that's a good question. And that is, I'm gonna take that one. It basically is to style the custom element itself so if you think of a custom element it has basically like its shell of a custom element and host is that element itself not the things inside of it. And something you can do, fair warning, is if I'd written it like, oh actually I should have put it down here, something like this wouldn't work. I think because you cannot really, would this has worked? That definitely works, yes. So it's a function, I guess. Not a... It's like a pseudo... Not a matcher in the classical sense. So that's something to look out for. It also can't go down multiple children I think only top level children. It's a little bit iffy to have good documentation on developers.google.com. Which you should totally go to and read up on this. And yeah, as I said, these red squares are a little bit sad. So what I'm gonna do instead I thought we could also do... An animation? No, not yet. But maybe later. Maybe I will humor you. You're not a one trick pony. I thought I would do the medium bit where they have the blurred version of it. Like a low res base 64 image background from Twitter's suggestion? Do you think we should do this? Oh, someone has been thinking along. I like it. That is exactly what I'm gonna do. So we are gonna generate a thumb. And because, as I said, the library is async, we have to create a new promissify function again. So I'm gonna call resize. Dot to buffer. Dot bind to the image because otherwise it doesn't work. And then our thumb will be the thumb... thumbfunk. How do you say thumb in German? Chrome home. Hey, Chrome home, how do you say thumb in German? Ein thumb. It's actually Daumen, but that's all right. So I thought I would do a thumb size because we can probably play around with the resolution a little bit. So I'm just gonna put it here. Thumb size is gonna go with 8 because that seems reasonable. Thumb is now the image buffer. Actually, that's not true. Or is it? That is something that needs to go here, if I remember correctly. And here we need to say png. There we go. Do you have all your brackets? Do you need an extra bracket? I think so. I think I'm good. It's not complaining. And so our thumb URL, what we're gonna do is we're gonna encode the thumb version as a base64 inline URL because we don't want to wait for the network to load a low-res version so we can then show the high-res version. So we're just gonna put it inline into the document right away. So when the HTML arrives, we have something on screen which I think is a much better experience. So the thumb... Somebody on Twitter wants me to do the animation stuff. You guys, I don't know how to animate anything. Like a transform is too hard for me. I really should. We're not gonna do much transform today, I think. Yeah, I know. And luckily, nodes in contrast to the web has just to base64, which is really convenient. Nice. And what we're gonna do here is we're gonna do, say, our background URL, background image is a URL and in here we're gonna do thumb URL. Boom. This looks about right, I think. I'm still on the slow network so we can actually see the loading pattern or I'm actually did a mistake. I probably did a mistake. That's not defined. Why not? No. But it's right here. Oh, that is... It should just be thumb. Thank you. A Zehen-nagel. Zehen-nagel? That wasn't quite what I was hoping for. I think it's actually correct because it's just an 8x8 image tiled all over the place, but that's not the visual we were looking for. Gotta stretch that out. We're gonna do, in the inherent styles, I'm gonna say background size is 100%, 100%. So we're on fast view. This is pretty good, but now, your moment, what could we do next? I'm gonna answer a question from Twitter. One of the questions was why didn't you just distribute an image as a slot in here? That would be annoying, wouldn't it, for every image that you wanted on your screen? Yeah, also that wouldn't be framework compatible because then if you, whenever you have something out of my trends, in general, it is rarely advisable to just sprout new children into a custom element dynamically. No, but you could have had the SC image, and then you would also put you as the page author, put your image in there. That's just annoying. You're writing the image twice. Yeah, no, I like this better. Yeah, just do it as a child. To be fair, that would work as well. I feel like it would be more work for me. I would copy and paste it twice. Okay, so I'm gonna ask you again. What could we do next? Put on your hat and get a clap out of people? Yes. I genuinely don't know the answer to this question. I think people might not be quite awake. It still works. Okay, so I'm gonna ask you, what are we gonna do next? This is your moment. We can do some animation. We can do some animations! Actually, it's gonna be super, super easy because the thing that annoys me a little bit is you have this nice blurred version and it's gonna be like... It's gonna be a little bit nicer. Are you gonna do a transform over there? No. You're wrong. So what I'm gonna do instead is we're just gonna... There's a fight on Twitter about how to properly translate thumb and thumbnail for the record. I'm gonna get in on that later. Yeah, yeah. So I'm gonna define a keyframe animation which goes from opacity zero and that's it. What does it go to? It goes. So I'm gonna put this on the image and the nice thing about this is that this way the browser will know it's an animation. It will do the whole promotion to its own layer and make it fast. And if you're writing production code, you'll put all the other vendor prefixes for keyframes and all that jazz. You have tools for that, right? I don't write those by hand. I do. We have forgotten the most important thing is we don't copy it. We never do production ready code. It's super charged. This is about concepts and things you can do on the web, but you shouldn't be copy pasting this. We didn't do accessibility. We did not do accessibility. I didn't reflect on my properties correctly. I only have gathers and no setters, which is also not nice. We're not even handling if you change the source on an element. If you're scrolling really fast and you're creating children a lot. You can't do that. You can't do that. It's so easy to load images lazy and have a nice transition on it, not to have an element that you can use everywhere. But still, I think the concept is pretty interesting. The last thing I'm going to do is I'm going to put an animation duration of five seconds on it mostly because I want to see the thing happening, not because five seconds is a good value here. But this should be enough to actually have the image just fade in because by default, the opacity default value is one. So I'm going to go ahead and I'm going to go ahead and transition to the default value. And secondly, animations don't loop by default. So I don't have to worry about the fade going over which is going to fade to the end position and stop there. So hopefully, we're going to see a blurred image. It's going to load and then it's going to fade in. Right? I'm going to scroll down. We can actually do the swaddling now because we don't want to wait that long. It makes a much nicer experience. That's wonderful. And just because we have a couple of minutes left, I'm just going to show you one thing. If you're more into the pixelated look, one property is all it takes. And I'm wrong. There you go. And that's something when you like increase the resolution a little bit on the thumbnails. Let's go to 16 by 16 because why not? You can actually see the patterns already emerging a little bit which also can be a really nice look. And I think I'm going to stop here. I'm going to push this code up on GitHub as we always do. It's on github.com Google Chrome. We're also going to put it in the description on the video. Thanks everyone so much for watching this, for bearing with me through all the weird phases of this. Thank you for handing the Twitter and confusing the heck out of me and making me use the hat twice. Thank you for clapping along. Could have been third times. If you have any questions, ask me on Twitter later or I'll be around a little bit more. Thank you.