GIFs are forever… Let’s make them better! CascadiaJS 2024

1987

1995

2005

2015

2024

2024

2024

256 Colors Per Frame?

256 Colors Per Frame?

256 Colors Per Frame? Single-Bit Transparency?

256 Colors Per Frame? Single-Bit Transparency?

256 Colors Per Frame? Single-Bit Transparency? Image Sequences?

256 Colors Per Frame? Single-Bit Transparency? Image Sequences?

Here is a cool example of a silent GIF I like this loop of a kitty with a soccer ball even better, though

“Using emoji, emoticons and GIFs in a texted conversation instantly signals the difference between sincerity and a joke or sarcasm.” — Jenna Wortham The New York Times

Tired: GIF, an image file format

Wired: GIF, a shorthand for silent, inline animated clips

<img src=”clip.gif” … … width=”…” height=”…”>

Animated Silent Looping Inline Autoplay

Animated Silent Looping Inline Autoplay

Animated Silent Looping Inline Autoplay Text alternative

Animated Silent Looping Inline Autoplay Text alternative

Of the top 1,000,000 home pages… Source: webaim.org/projects/million (March 2024)

Of the top 1,000,000 home pages… 54.5% were missing alt text! Source: webaim.org/projects/million (March 2024)

Of the top 1,000,000 home pages… 54.5% were missing alt text! Source: webaim.org/projects/million (March 2024)

Describe the image for blind and low-vision users A peeved groundhog repeatedly shouts “Hey!…Hey!…Hey!”

cloudfour.com/thinks/write-alt-text-like-youre-talking-to-a-friend

<img src=”clip.gif” … … width=”…” height=”…” alt=”A hypothetical example for this slide.” />

Animated Silent Looping Inline Autoplay Text alternative

Animated Text alternative Silent Looping Higher quality Inline Autoplay

Animated Text alternative Silent Looping Higher quality Smaller size Inline Autoplay

Animated Text alternative Silent Looping Higher quality Smaller size Inline Autoplay

WebP Since 2014 (v32) Since 2018 (v18) Since 2019 (v65) Since 2022 (v16.0)

WebP Since 2014 (v32) Since 2018 (v18) Since 2019 (v65) Since 2022 (v16.0) AVIF Since 2020 (v85) This Year (v121) Since 2021 (v93) Since 2023 (v16.4)

974 KB 226 KB 66 KB GIF WebP AVIF

974 KB r e h g Hi ! ! y t i qual 226 KB 66 KB GIF WebP AVIF

Local libvips sharp gif2webp FFmpeg ImageMagick

s e c i v r e S Local libvips sharp gif2webp FFmpeg ImageMagick : e r U S o l c s i d L Ful ! T n e i l c They’rE a cloudfour.com/made …and many others!

<img src=”clip.avif” clip.gif … … width=”…” height=”…” alt=”A hypothetical animated AVIF asset.” />

<img src=”clip.avif” … … width=”…” height=”…” alt=”A hypothetical animated AVIF asset.” />

<picture> <source type=”image/avif” srcset=”clip.avif” /> <source type=”image/webp” srcset=”clip.webp” /> <img src=”clip.gif” … … width=”…” height=”…” alt=”Possibly AVIF, WebP or GIF” /> </picture>

Animated Text alternative Silent Looping Higher quality Smaller size Inline Autoplay

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay

clip.avif f i v a . c i t a t s

<picture> <source media=”(prefers-reduced-motion: no-preference)” srcset=”clip.avif” /> <img src=”static.avif” … … width=”…” height=”…” alt=”This meme may or may not be animated.” /> </picture>

<picture> <source media=”(prefers-reduced-motion: no-preference)” srcset=”clip.avif” /> <img src=”static.avif” … … width=”…” height=”…” alt=”This meme may or may not be animated.” /> </picture>

<picture> <source media=”(prefers-reduced-motion: no-preference)” srcset=”clip.avif” /> <img src=”static.avif” … … width=”…” height=”…” alt=”This meme may or may not be animated.” /> </picture>

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Motion sensitivity often depends on content + context

≠

≠

<video controls>

MP4 (H.264) Since 2010 (v4) Since 2015 (v12) Since 2015 (v35) Since 2008 (v3.2)

MP4 Since 2010 (v4) Since 2015 (v12) Since 2015 (v35) Since 2008 (v3.2) WebM Since 2013 (v25) Since 2020 (v79) Since 2014 (v28) Since 2022 (v16.0) (H.264) (VP8 / VP9)

974 KB 226 KB GIF WebP 81 KB 66 KB 59 KB MP4 AVIF WebM (H.264) (VP9)

Local FFmpeg Handbrake Adobe Media Encoder

s e c i v r e S Local FFmpeg Handbrake Adobe Media Encoder : e r U S o l c s i d L Ful ! d E s A i b I’m stiLl cloudfour.com/made …and even more alternatives!

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” … … width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline … … width=”…” height=”…”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video>

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

<img src=”clip.avif” … … width=”…” height=”…” alt=”I have alternative text!” />

<img src=”clip.avif” … … width=”…” height=”…” alt=”I have alternative text!” /> <video controls autoplay loop muted playsinline src=”clip.mp4” width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” width=”…” height=”…”> </video>

<video controls autoplay loop muted playsinline src=”clip.mp4” width=”…” height=”…”> </video>

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

<video controls autoplay loop muted playsinline … … width=”…” height=”…”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video>

<figure> <video controls autoplay loop muted playsinline … … width=”…” height=”…”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video> <figcaption> An example of a video used as a GIF. </figcaption> </figure>

<video controls autoplay loop muted playsinline … … width=”…” height=”…” aria-label=”An example of a video used as a GIF.”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video>

<video controls autoplay loop muted playsinline … … width=”…” height=”…” aria-labelledby=”clip-label”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video> <div id=”clip-label” aria-hidden=”true”> An example of a video used as a GIF. </div>

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

<picture> <source media=”(prefers-reduced-motion: no-preference)” srcset=”clip.avif” /> <img src=”static.avif” … … width=”…” height=”…” alt=”This meme may or may not be animated.” /> </picture>

<video controls autoplay loop muted playsinline … … width=”…” height=”…” aria-labelledby=”clip-label”> <source src=”clip.webm” type=”video/webm” media=”(prefers-reduced-motion: no-preference)” /> <source src=”clip.mp4” type=”video/mp4” media=”(prefers-reduced-motion: no-preference)” /> </video> <div id=”clip-label” aria-hidden=”true”> An example of a video used as a GIF. </div>

Web components!

<video controls autoplay loop muted playsinline … … width=”…” height=”…” aria-labelledby=”clip-label”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video> <div id=”clip-label” aria-hidden=”true”> An example of a video used as a GIF. </div>

<video controls loop autoplay muted playsinline … … width=”…” height=”…” aria-labelledby=”clip-label”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video> <div id=”clip-label” aria-hidden=”true”> An example of a video used as a GIF. </div>

<gif-like> <video controls loop muted playsinline … … width=”…” height=”…” aria-labelledby=”clip-label”> <source src=”clip.webm” type=”video/webm” /> <source src=”clip.mp4” type=”video/mp4” /> </video> <div id=”clip-label” aria-hidden=”true”> An example of a video used as a GIF. </div> </gif-like>

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) { if (state) { this.video.play(); } else { this.video.pause(); } } } customElements.define(“gif-like”, GifLike);

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) {

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) {

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) {

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) {

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) {

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) {

this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) { if (state) { this.video.play(); } else { this.video.pause(); } } } customElements.define(“gif-like”, GifLike);

toggle(state) { if (state) { this.video.play(); } else { this.video.pause(); } } } customElements.define(“gif-like”, GifLike);

class GifLike extends HTMLElement { static motionQuery = window.matchMedia( “(prefers-reduced-motion: no-preference)” ); connectedCallback() { this.video = this.querySelector(“video”); GifLike.motionQuery.addEventListener(“change”, (query) => { this.toggle(query.matches); }); this.toggle(GifLike.motionQuery.matches); } toggle(state) { if (state) { this.video.play(); } else { this.video.pause(); } } } customElements.define(“gif-like”, GifLike); ! e r E h d SHaRe gist.github.com/tylersticka

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

Animated Text alternative Silent Looping Higher quality Smaller size Inline Motion prefs Autoplay Playback control

web.dev/articles/lazy-loading-video

… … <gif-like src=”…” alt=”…”></gif-like> r o c B e w y t n e v e Enhance SSR or el … r e p l e h e t a l p m e t cMs block or <gif-like> <video controls loop muted playsinline preload=”none” … poster=”…” … … width=”…” height=”…”

Here is a cool example of a silent GIF I like this loop of a kitty with a soccer ball even better, though GIF is a shorthand for silent, inline, animated clips

Alternative text is chronically overlooked low-hanging fruit

974 KB Newer formats work in all modern browsers, load faster and look better 226 KB GIF WebP 81 KB 66 KB 59 KB MP4 AVIF WebM (H.264) (VP9)

Video gives users more control

<gif-like> Web components are a great choice for progressive enhancement </gif-like>

tylersticka.com @$tylersticka$@$social.lol /in/$tylersticka ! m a e t Hire my cloudfour.com