Reimplementing Deprecated
HTML Elements
with Web Components
(An Ode to GeoCities)
Who Am I?
Alex Riviere
- Senior Frontend Developer
- Co-Host of Enjoy the Vue Podcast
- Twitch Streamer
- Atlanta Vue.js Meetup and PyATL Co-Organizer
Let's take a trip back in time....
A Brief History of the Web
- Nov. 1995 - HTML 2.0 RFC published
- Aug. 1996 - IE3 released with CSS support
- Jan. 1997 - HTML 3.2 Spec published
A Brief History of the Web
This is the time period that gave us
<marquee> <blink> <font> <center>
and other well loved tags
A Brief History of the Web
Dec. 1997
HTML 4.0 RFC published
deprecates a lot of tags
A Brief History of the Web
- Mid 1995 - BHI (Beverly Hills Internet) is formed by David Bohnett and John Rezner
- 2MB of free web hosting
- users could join a "neighborhood"
- focused on community
- Dec 1995 - BHI rebrands to
GeoCities
GeoCities
Let's jump ahead a bit
2013 - customElements Proposal
Make your own html elements!
2018 - customElements available in most browsers
(We can probably use this now in 2022.)
Let's jump ahead a bit
2021 - I got bored and nostalgic
(This will not end well.)
geo-elements
geo-elements
- Experience the joy of HTML3 with good accessibility
- focus on your html, and nothing else
- (it's probably a bad idea to use this.)
How to make
Custom Elements
Let's make a <blink> Tag
class GeoElementBlink extends HTMLElement{
constructor(){
super();
this.attachShadow({mode:'open'});
const style = document.createElement('style');
const slot = document.createElement('slot');
style.textContent = `
:host{ font-size:inherit; line-height:inherit;}
slot{ animation: 2s linear infinite condemned_blink_effect; }
@keyframes condemned_blink_effect { 0% {visibility: hidden;} 50% {visibility: hidden;} 100% {visibility: visible;} }
@media (prefers-reduced-motion) {
slot { animation:none; text-shadow: 0 0 0.1rem red, 0 0 0.2rem orange, 0 0 0.3rem yellow, 0 0 0.4rem green, 0 0 0.5rem blue, 0 0 0.6rem indigo, 0 0 0.7rem violet;}
}`;
this.shadowRoot.append(style,slot);
}
}
customElements.define('ge-blink', GeoElementBlink);
Let's make a <blink> Tag
quick review
- create a by extending an HTMLElement
- always call super() first in your constructor
-
use this.attachShadow({mode:'open'}) to attach shadowDOM
-
use this.shadowRoot.append to add elements to the shadowDOM
-
make sure to add a slot element to display child content.
-
register your new element with window.customElements.define
Let's talk about attributes
Let's make a <background> Tag
class GeoElementBackground extends HTMLElement {
static get observedAttributes() {
return ["background", "bgcolor", "inline"];
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case "background":
this.updateImage(newValue);
break;
case "bgcolor":
this.updateColor(newValue);
break;
case "inline":
this.updateInline(newValue);
break;
}
}
constructor() {
super();
this.attachShadow({ mode: "open" });
this.imageStyle = document.createElement("style");
this.colorStyle = document.createElement("style");
this.inlineStyle = document.createElement("style");
const slot = document.createElement("slot");
this.shadowRoot.append(
this.imageStyle,
this.colorStyle,
this.inlineStyle,
slot
);
this.updateImage();
this.updateColor();
this.updateInline();
}
updateStyle(type, value) {
this[type + "Style"].textContent = value;
}
updateImage(value) {
if (!value) {
this.updateStyle("image", "");
} else {
this.updateStyle("image", `:host{background-image:url(${value});}`);
}
}
updateColor(value) {
if (!value) {
this.updateStyle("color", `:host{background-color: transparent;}`);
} else {
this.updateStyle("color", `:host{background-color: ${value};}`);
}
}
updateInline(value) {
if (!value) {
this.updateStyle(
"inline",
`:host{display:block; min-height: 100vh; min-width: 100vw;}`
);
} else {
this.updateStyle("inline", `:host{display:inline-block;}`);
}
}
}
customElements.define("ge-background", GeoElementBackground);
Let's make a <background> Tag
quick review
- static get observedAttributes() returns an array of attributes that should be watched for changes
- attributeChangedCallback will be called each time an observedAttribute is modified.
Let's Make a
<marquee> is weird
<marquee> is weird
<marquee> is weird
Let's make a <simple-marquee>
Let's make a <simple-marquee>
class SimpleMarquee extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
const style = document.createElement("style");
this.divElement = document.createElement("div");
this.divElement.append(document.createElement("slot"));
style.textContent = `:host {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-align: initial;
white-space: nowrap;
}
div{
display: inline-block;
overflow: hidden;
text-align: initial;
white-space: nowrap;
}
`;
this.shadowRoot.append(style, this.divElement);
const animation = this.divElement.animate(
[
{ transform: `translateX(${this.clientWidth}px)` },
{ transform: `translateX(-${this.divElement.scrollWidth}px)` }
],
{
duration: (this.offsetWidth+this.divElement.scrollWidth) / (6/85),
iterations: Infinity
}
);
}
}
customElements.define("simple-marquee", SimpleMarquee);
Let's make a <simple-marquee>
Let's make a <simple-marquee>
-
connectedCallback
-
adoptedCallback
-
disconnectedCallback
Let's FIX <simple-marquee>
class SimpleMarquee extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
const style = document.createElement("style");
this.divElement = document.createElement("div");
this.divElement.append(document.createElement("slot"));
style.textContent = `:host {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-align: initial;
white-space: nowrap;
}
div{
display: inline-block;
overflow: hidden;
text-align: initial;
white-space: nowrap;
}
`;
this.shadowRoot.append(style, this.divElement);
this.currentAnimation = null;
}
startAnimation() {
if (this.currentAnimation) {
this.currentAnimation.cancel();
}
this.currentAnimation = this.divElement.animate(
[
{ transform: `translateX(${this.clientWidth}px)` },
{ transform: `translateX(-${this.divElement.scrollWidth}px)` }
],
{
duration: (this.offsetWidth + this.divElement.scrollWidth) / (6 / 85),
iterations: Infinity
}
);
}
connectedCallback() {
this.startAnimation();
}
adoptedCallback() {
this.startAnimation();
}
disconnectedCallback() {
if (this.currentAnimation) {
this.currentAnimation.cancel();
}
delete this.currentAnimation;
}
}
customElements.define("simple-marquee", SimpleMarquee);
Let's FIX <simple-marquee>
Let's Review
- Use the connectedCallback method to handle being added to the dom
- Use the adoptedCallback to handle being moved from one document to another (generally between iframes)
- Use disconnectedCallback to clean up anything before an element is destroyed
What's next?
- Lit - https://lit.dev
- Stencil - https://stenciljs.com/
- Vue 3 - https://vuejs.org/guide/extras/web-components.html
- Svelte - https://svelte.dev/docs#run-time-custom-element-api
- Angular - https://angular.io/guide/elements
- React - https://reactjs.org/docs/web-components.html
Use a Framework
Thank you.
For More Terrible Things:
- My Twitch: @fimion
- My Blog: alex.party
- My Twitter: @fimion
Reimplementing Deprecated HTML Elements - Atlanta Dev Conf 2022
By Alex Riviere
Reimplementing Deprecated HTML Elements - Atlanta Dev Conf 2022
- 450