I'm new using web component. I defined a MenuComponent:
menu-component.html:
<link rel="stylesheet" href="menu-component/menu-component.css">
<div class="mobile-menu">
<div></div>
<div style="width: 100%;"></div>
<div></div>
</div>
<div class="mobile-page-title"></div>
<a href="index.html">
<div class="logo">
<div style="letter-spacing: .01rem; padding-right: 0.1rem;">creativ</div>
<div class="logo-accent" style="font-size: 2.8rem; line-height: 2rem; align-self: flex-start;">3</div>
<div class="logo-accent" style="letter-spacing: 0.1rem;">ly</div>
</div>
</a>
<div class="menu-container">
<div class="menu-center">
<div id="home-menu" class="menu-item" style="margin-top: 0.5rem;">
<a href="index.html">home</a>
</div>
<div class="menu-underline"></div>
</div>
<div class="menu-center">
<div id="design-menu" class="menu-item first-menu">
<a href="designs.html">designs</a>
</div>
<div class="menu-underline"></div>
</div>
<div class="slide-right2 menu-center">
<div id="logos-menu" class="menu-item">
<a href="logos.html">logos</a>
</div>
<div class="menu-underline"></div>
</div>
<div class="slide-right3 menu-center">
<div id="paintings-menu" class="menu-item">
<a href="paintings.html">paintings</a>
</div>
<div class="menu-underline"></div>
</div>
<div class="slide-right4 menu-center">
<div id="bio-menu" class="menu-item">
<a href="bio.html">bio</a>
</div>
<div class="menu-underline"></div>
</div>
<div class="slide-right5 menu-center">
<div id="contacts-menu" class="menu-item">
<a href="contacts.html">contacts</a>
</div>
<div class="menu-underline"></div>
</div>
</div>
menu-component.css:
a {
color: unset;
text-decoration: none;
cursor: unset;
}
.logo {
width: fit-content;
padding: 0 1rem;
font-size: 1.8rem;
display: flex;
align-items: baseline;
top: 2rem;
position: fixed;
cursor: pointer;
z-index: 2000;
}
.logo-accent {
color: var(--brand-color);
font-family: var(--accent-font-family);
font-style: italic;
font-size: 2.37rem;
}
.mobile-page-title {
display: none;
color: var(--background-color);
font-family: var(--accent-font-family);
font-style: italic;
font-size: 1.4rem;
background-color: var(--brand-color);
padding: 0.5rem 1rem 0.6rem 3rem;
border-radius: 0 20px 20px 0;
top: 0px;
position: fixed;
z-index: 2000;
}
.no-underline {
border-bottom: 2px solid var(--brand-color);
}
.underline {
border-bottom: 2px solid var(--complementary-color);
transition: width 2s;
width: 3%;
}
.menu-item {
color: var(--background-color);
font-family: var(--accent-font-family);
padding-top: 1.2rem;
cursor: pointer;
font-style: italic;
animation: 2s slide-right;
}
.menu-center {
display: flex;
flex-direction: column;
width: 100%;
overflow: hidden;
align-items: center;
}
.menu-underline {
height: 4px;
border-top: 2px solid var(--complementary-color);
box-sizing: border-box;
width: 0%;
transition: .5s;
}
.menu-item:hover + .menu-underline {
width: 100%;
}
.mobile-menu {
display: none;
}
.first-menu {
margin-top: 0.5rem;
}
#home-menu {
display: none;
}
.menu-container {
width: 165px;
position: fixed;
top: 50%;
transform: translate(0, -50%);
background-color: var(--brand-color);
border-radius: 0 0.5rem 0.5rem 0;
padding: 0 1em 1.5rem 1rem;
font-size: 1.5rem;
line-height: 1.5;
box-sizing: border-box;
letter-spacing: .03em;
}
.slide-right {
width: 100%;
overflow: hidden;
text-align: center;
}
.slide-right2 div {
animation-delay: 0.5s;
animation-fill-mode: backwards;
}
.slide-right3 div {
animation-delay: 1s;
animation-fill-mode: backwards;
}
.slide-right4 div {
animation-delay: 1.5s;
animation-fill-mode: backwards;
}
.slide-right5 div {
animation-delay: 2s;
animation-fill-mode: backwards;
}
@keyframes slide-right {
from {
margin-left: -180%;
}
to {
margin-left: 0%;
}
}
@media only screen and (max-width: 768px) {
/* For mobile phones: */
.menu-container {
top: 0;
transform: none;
display: none;
z-index: 2002;
animation: 1s menu-opening ease-in-out forwards;
}
@keyframes menu-opening {
0% {transform: scale(0); transform-origin: top left;}
100% {transform: scale(1);}
}
.logo {
transform: scale(0.8);
top: 0;
right: 0;
/* position: absolute; */
padding: 0;
animation: 1s scale-logo ease-in-out;
animation-fill-mode: backwards;
}
@keyframes scale-logo {
0% {transform: scale(0.8);}
100% {transform: scale(1);}
}
.mobile-menu {
margin: 1rem 0.5rem 0 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
position: fixed;
width: 25px;
animation: 1s scale-mobile-menu ease-in-out;
animation-fill-mode: backwards;
z-index: 2000;
justify-content: space-between;
height: 1.2rem;
z-index: 2001;
}
@keyframes scale-mobile-menu {
0% {transform: scale(1);}
100% {transform: scale(1.3);}
}
.mobile-menu div {
width: 80%;
height: 3px;
background-color: var(--brand-color);
border-radius: 1px;
}
.menu-item {
animation: none;
}
.first-menu {
margin-top: 0;
}
#home-menu {
display: block;
}
}
menu-component.js
fetch("menu-component/menu-component.html")
.then(stream => stream.text())
.then(text => define(text));
function define(html) {
class MenuComponent extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = html;
let menuContainer = shadow.querySelectorAll(".menu-container")[0];
document.addEventListener('click', (event) => {
if (event.target !== this) {
this.closeMobileMenu(menuContainer);
}
});
let menuItems = shadow.querySelectorAll(".menu-item");
for (let menuItem of menuItems) {
menuItem.addEventListener('click', this.onMouseClick);
}
let mobileMenu = shadow.querySelectorAll(".mobile-menu")[0];
mobileMenu.addEventListener('click', this.onMobileMenuClick);
//set the menu with the name of the page if it is not home page
const url = window.location.href;
const index = url.lastIndexOf("/");
const urlPage = url.substring(index+1);
let bodyWidth = document.body.clientWidth;
console.log(bodyWidth);
if (!urlPage.includes('index') && urlPage !== '') {
let pageTitle = shadow.querySelectorAll(".mobile-page-title")[0];
if (bodyWidth <= 750) {
let pageNameExt = url.split("/").pop();
let pageName = pageNameExt.split(".")[0];
if (pageName.includes("design")) {
pageName = "designs";
} else if (pageName.includes("logo")) {
pageName = "logos";
}
pageTitle.innerHTML = pageName;
pageTitle.style.display = 'inline';
mobileMenu.children[0].style.backgroundColor = 'var(--background-color)';
mobileMenu.children[1].style.backgroundColor = 'var(--background-color)';
mobileMenu.children[2].style.backgroundColor = 'var(--background-color)';
} else {
pageTitle.style.display = 'none';
}
for (let menuItem of menuItems) {
menuItem.style.animation = 'none';
let id = menuItem.getAttribute('id').split('-')[0];
if (url.includes(id)) {
menuItem.nextElementSibling.style.width = "100%";
menuItem.nextElementSibling.style.borderTop = "3px solid var(--complementary-color)";
}
}
mobileMenu.style.animation = 'none';
let logo = shadow.querySelectorAll(".logo")[0];
logo.style.animation = 'none';
}
}
}
customElements.define('menu-component', MenuComponent);
}
In may html page I use it this way (this is the not-found page):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<script type="module" src="menu-component/menucomponent.js"></script>
<title>Page not found</title>
</head>
<body>
<menu-component></menu-component>
<div class="content-container">
<div class="page-title">
Content not found
</div>
<div class="inner-container">
<div class="text-description">
It looks like there is a problem with the page you want to see. Please try with another content.</a>
</div>
</div>
</div>
</body>
</html>
I noticed that I had a problem with styling the menu-component element: for an instant the element is displayed without style (no css applied) and then is rendered. I think this is the wel known problem of web component called FOUC. I followed advices and I added the following rules in my style.css:
menu-component:not(:defined) {
opacity: 0;
}
menu-component {
opacity: 1;
transition: opacity 0.5s ease-in-out;
}
This looks like solving the problem, but not always. In chrome and firefox it's visible after a hard reload. In Safari it's always visible. How can I solve it?
I was expecting that the code in style.css resolved the problem.
<link>
loads your CSS asynchronous, so the menu-component will get opacity:1
before all CSS is applied.
Use the <link onload="...">
event to apply styles after the CSS file was loaded (and parsed)
<my-component>Hello! Web Component</my-component>
<script>
customElements.define("my-component", class extends HTMLElement {
constructor() {
const element = (tag, props = {}) => Object.assign(document.createElement(tag), props);
super()
.attachShadow({mode:"open"})
.append(
this.STYLED = element("STYLE", { innerHTML: `:host{opacity:0}` }),
element("STYLE", { innerHTML: `:host{display:inline-block}` }),
element("LINK", {
rel: "stylesheet",
href: "https://cdn.sstatic.net/Sites/stackoverflow/primary.css",
onload: (evt) => {
console.log("loaded CSS");
this.STYLED.innerHTML = `:host{transition:opacity 3s}`
}
}),
element("SLOT"),
element("P", {
className: `is-required`,
innerHTML: `P styled by stackoverflow/primary.css`
})
);
console.log("created shadowDOM");
}
connectedCallback() {
console.log("connectedCallback")
}
});
</script>
In your code both HTML and CSS could be fetched by One Web Component.
You have given them the same name already.
You can get the calling component with:
<link rel="stylesheet" onload="console.warn(this.getRootNode().host)"
href="menu-component/menu-component.css">
But preferably let the Web Component handle loading CSS by injecting the <link>
tag