How to position nested shadow DOMs on top of each other? - css

Is it possible to position nested shadow DOMs on top of each other?
With normal DOM, nested divs just stack by default:
.d1 {
width: 150px;
height: 150px;
background-color: lightseagreen;
}
.d2 {
width: 100px;
height: 100px;
background-color: darkslateblue;
}
.d3 {
width: 50px;
height: 50px;
background-color: lightgray;
}
<div class="container">
<div class="d1">
<div class="d2">
<div class="d3">
</div>
</div>
</div>
</div>
But with shadow DOM, how can this be done?
id = 0;
depth = 3;
customElements.define('box-component', class extends HTMLElement {
constructor() {
super();
if (id > depth)
return;
this.id = id++;
var template = document.getElementById("box-template");
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = template.innerHTML;
// The only goal of all of the following boilerplate is to set 'exportparts' attribute,
// such that each parent exports its *descendants* (not just *children*) parts.
// This is done by accessing the shadow dom of the child from its parent.
// (note: the outermost component doesn't need to export its parts)
if (id == 0)
return;
const parts = [...this.shadowRoot.children]
.filter(elem => elem.getAttribute('part'))
let thisExportParts = null;
let childExportParts = null;
thisExportParts = parts.map(elem => [
[elem.getAttribute('part'), `${elem.getAttribute('part')}${this.id}`].join(':')
].join(','));
if (this.shadowRoot && this.shadowRoot.children) {
const childShadowRoot =
[...this.shadowRoot.children].filter(elem => elem.shadowRoot)[0];
if (childShadowRoot) {
const fullChildExportParts = childShadowRoot.getAttribute('exportparts');
if (fullChildExportParts) {
childExportParts = fullChildExportParts.split(',').map(part => {
return part.includes(':') ? part.split(':')[1] : part;
}).join(',');
}
}
}
this.setAttribute('exportparts', [thisExportParts, childExportParts].filter(_ => _).join(','));
}
});
box-component::part(box1) {
width: 150px;
height: 150px;
background-color: lightseagreen;
}
box-component::part(box2) {
width: 100px;
height: 100px;
background-color: darkslateblue;
}
box-component::part(box3) {
width: 50px;
height: 50px;
background-color: lightgray;
}
<template id="box-template">
<style>
</style>
<div part="box"></div>
<box-component></box-component>
</template>
<box-component class="container"></box-component>

It's possible. In the shadow DOM, the following should be added:
:host {
position: absolute;
top: 0;
}
This :host rule styles all instances of the custom component element (the shadow host) in the document to be absolutely positioned, while also making the positioning relative to each positioned parent (that's why top: 0 is needed).
In fact, this is similar to changing the normal DOM code in the question to use positioning (note that without positioning, just setting top: 0 won't work (further read)):
.container {
position: relative;
}
.container div {
position: absolute;
top: 0;
}
.d1 {
width: 150px;
height: 150px;
background-color: lightseagreen;
}
.d2 {
width: 100px;
height: 100px;
background-color: darkslateblue;
}
.d3 {
width: 50px;
height: 50px;
background-color: lightgray;
}
<div class="container">
<div class="d1">
<div class="d2">
<div class="d3">
</div>
</div>
</div>
</div>
So, here's the full code:
id = 0;
depth = 3;
customElements.define('box-component', class extends HTMLElement {
constructor() {
super();
if (id > depth)
return;
this.id = id++;
var template = document.getElementById("box-template");
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = template.innerHTML;
// The only goal of all of the following boilerplate is to set 'exportparts' attribute,
// such that each parent exports its *descendants* (not just *children*) parts.
// This is done by accessing the shadow dom of the child from its parent.
// (note: the outermost component doesn't need to export its parts)
if (id == 0)
return;
const parts = [...this.shadowRoot.children]
.filter(elem => elem.getAttribute('part'))
let thisExportParts = null;
let childExportParts = null;
thisExportParts = parts.map(elem => [
[elem.getAttribute('part'), `${elem.getAttribute('part')}${this.id}`].join(':')
].join(','));
if (this.shadowRoot && this.shadowRoot.children) {
const childShadowRoot =
[...this.shadowRoot.children].filter(elem => elem.shadowRoot)[0];
if (childShadowRoot) {
const fullChildExportParts = childShadowRoot.getAttribute('exportparts');
if (fullChildExportParts) {
childExportParts = fullChildExportParts.split(',').map(part => {
return part.includes(':') ? part.split(':')[1] : part;
}).join(',');
}
}
}
this.setAttribute('exportparts', [thisExportParts, childExportParts].filter(_ => _).join(','));
}
});
box-component::part(box1) {
width: 150px;
height: 150px;
background-color: lightseagreen;
}
box-component::part(box2) {
width: 100px;
height: 100px;
background-color: darkslateblue;
}
box-component::part(box3) {
width: 50px;
height: 50px;
background-color: lightgray;
}
<template id="box-template">
<style>
:host {
position: absolute;
top: 0px;
}
</style>
<div part="box"></div>
<box-component></box-component>
</template>
<box-component class="container"></box-component>

Related

Sidebar slides in and out on page refresh

I created a sidebar (hidden by default) in a web store that contains a list of items you like. When clicked on a button the likes-list will show sliding in from the right smoothly. However on page refresh/reload the sidebar slides in and out rapidly. I'm using css animation to do the trick.
It works fine but somehow on page reload the animation is triggered without clicking the button. How to prevent this from happening?
App is built in React (create-react-app).
React component
import React from 'react';
import { useGlobalContext } from '../Context'
import './likes.css';
const Likes = ({ showLikes, setShowLikes }) => {
let { liked, setLiked, paintings, setPaintings } = useGlobalContext()
//Remove item from likes list
const removeItemFromLikeslist = (id) => {
liked = liked.filter(item => {
return item.id !== id;
});
//Change likes-heart to green
paintings = paintings.map(el => {
if (el.id === id) {
el.like = false
}
return el
})
//remove sidebar overlay
if (liked.length === 0) {
setShowLikes(false)
}
setPaintings(paintings)
setLiked(liked)
localStorage.setItem("PAINTINGS", JSON.stringify(paintings))
localStorage.setItem('LIKES', JSON.stringify(liked))
}
return (
<div className={showLikes ? "likesContainer show" : "likesContainer"} >
<div className="likesHeader">You like these paintings</div>
{liked.map(item => {
const { id, name, imgSrc } = item;
return (
<div className="like-item" onClick={() => removeItemFromLikeslist(id)} key={id}>
<div className="like-info">
<img src={imgSrc} style={{ width: "100px" }} alt={name} className="picInCart" />
</div>
<div className="like-name">
<h5>{name}</h5>
</div>
</div>
)
})}
</div>
)
}
export default Likes
CSS file
.likesContainer {
position: fixed;
top: 83px;
right: -328px;
width: 320px;
background-color: rgb(70, 70, 70);
height:auto;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
z-index: 2;
box-shadow: 0px 4px 10px black;
animation: animateHide .5s linear forwards;
}
#keyframes animateHide {
from {
right: 0;
}
to {
right: -328px;
}
}
.likesContainer.show {
right: 0;
animation: animateShow .5s linear forwards;
}
#keyframes animateShow {
from {
right: -328px;
}
to {
right: 0;
}
}
Problem solved, changed from animation to transition like so:
.likesContainer {
position: fixed;
top: 83px;
right: -328px;
width: 320px;
background-color: rgb(70, 70, 70);
height:auto;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
z-index: 2;
box-shadow: 0px 4px 10px black;
transition: .5s ease-in-out;
}
.likesContainer.show {
right: 0;
}

Parts of the background are forming a border around the red border

How come there are thin lines going around the whole border, and can they be removed?
It looks like parts of the background are forming a border around the red border.
Can that be fixed?
That is the whole issue
https://jsfiddle.net/59t3k1gn/
One is an image of it zoomed in on the border, and the other is not zoomed in.
How come the background is forming a border around the red border, and how is that fixed?
(function manageCurtain() {
"use strict";
function hide(el) {
el.classList.add("hide");
}
function coverClickHandler(evt) {
const cover = evt.currentTarget;
hide(cover);
const curtain = document.querySelector(".outer");
curtain.classList.add("slide");
//curtain.classList.add("fadeout");
const wrap = document.querySelector(".wrap");
wrap.classList.remove("hide");
}
const cover = document.querySelector(".play");
cover.addEventListener("click", coverClickHandler);
}());
const videoPlayer = (function makeVideoPlayer() {
"use strict";
let player = null;
const tag = document.createElement("script");
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
function onPlayerReady(event) {
player = event.target;
player.setVolume(100); // percent
}
let hasShuffled = false;
function onPlayerStateChange(event) {
player = event.target;
const shufflePlaylist = true;
if (!hasShuffled) {
player.setShuffle(shufflePlaylist);
player.playVideoAt(0);
hasShuffled = true;
}
}
function addPlayer(video) {
const playlist = "M7lc1UVf-VE";
const config = {
height: 360,
host: "https://www.youtube-nocookie.com",
width: 640
};
config.playerVars = {
autoplay: 0,
cc_load_policy: 0,
controls: 1,
disablekb: 1,
fs: 0,
iv_load_policy: 3,
loop: 1,
playlist,
rel: 0
};
config.events = {
"onReady": onPlayerReady,
"onStateChange": onPlayerStateChange
};
player = new YT.Player(video, config);
}
return {
addPlayer
};
}());
function onYouTubeIframeAPIReady() {
const cover = document.querySelector(".play");
const wrapper = cover.parentElement;
const frameContainer = wrapper.querySelector(".video");
videoPlayer.addPlayer(frameContainer);
}
(function iife() {
"use strict";
function show(el) {
el.classList.remove("hide");
}
function coverClickHandler(evt) {
const wrapper = evt.currentTarget.parentElement;
show(wrapper);
}
const cover = document.querySelector(".play");
cover.addEventListener("click", coverClickHandler);
}());
html,
body {
height: 100%;
padding: 0;
margin: 0;
}
body {
background: url(https://picsum.photos/id/1015/1500/1500) no-repeat;
background-attachment: fixed;
background-size: cover;
}
.fadeout .split-wrap {
opacity: 0;
transition: opacity 1s ease 3s, width 0s 10s, height 0s 10s;
}
.outer {
position: relative;
display: table;
height: 100%;
margin: 0 auto;
width: 100%;
overflow: hidden;
}
.tcell {
display: table-cell;
vertical-align: middle;
padding: 8px 8px;
}
.curtain {
max-width: 640px;
margin: auto;
border: 3px solid red;
box-sizing: border-box;
border-radius: 25px;
overflow: hidden;
background: transparent;
}
.curtain-ratio-keeper {
position: relative;
width: 100%;
height: 0;
padding-top: 56.25%;
overflow: hidden;
background: transparent;
}
.slide-wrap:before,
.slide-wrap:after {
content: "";
position: absolute;
top: 0;
width: 50%;
height: 100%;
transition: transform 5s linear;
background: url(https://picsum.photos/id/1015/1500/1500) no-repeat;
background-attachment: fixed;
background-size: cover;
}
.slide-wrap:before {
left: 0;
}
.slide-wrap:after {
right: 0;
}
.slide .slide-wrap::before {
transform: translateX(-100%);
}
.slide .slide-wrap::after {
transform: translateX(100%);
}
.video-frame {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
}
.play {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
width: 98px;
height: 98px;
border-radius: 50%;
fill: red;
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.7));
cursor: pointer;
}
.hide {
display: none;
}
<div class="outer">
<div class="tcell">
<div class="curtain">
<div class="curtain-ratio-keeper">
<div class="wrap hide">
<div class="video video-frame"></div>
</div>
<div class="slide-wrap"></div>
</div>
<svg class="play " width="100%" height="100%" viewBox="0 0 64 64">
<path d="M25.6,46.4L44.8,32L25.6,17.6V46.4z M32,0C14.3,0,0,14.3,0,32s14.3,32,32,32s32-14.3,32-32S49.7,0,32,0z
M32,57.6C17.9,57.6,6.4,46.1,6.4,32S17.9,6.4,32,6.4S57.6,17.9,57.6,32S46.1,57.6,32,57.6z" />
</svg>
</div>
</div>
</div>
Seems fine to me, what exactly is the issue? This effect you described might be either yous screen blurring pixels in certain zoom levels, or a visual illusion called Mach bands

::-webkit-scrollbar is it possible to make the scrollbar-thumb grow instead moving?

I would like my scroll to work like this when the user scrolls. e.g to start to fill up instead of moving.
Is it possible to make the scroll-thumb grow or to style the scrollbar-track-piece different before and after the thumb?
Here is a small example how to implement this loader
window.addEventListener("scroll", (e) => {
var html = document.documentElement;
let step = 100 / (html.scrollHeight - window.innerHeight);
let loader = document.getElementById("scrollprogress");
loader.style.width = (step * html.scrollTop) + "%";
})
#scrollprogress {
height: 5px;
position: fixed;
left: 0;
top: 0;
background: orange;
}
.backgr {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 5px;
background: lightgrey;
z-index: -1;
}
.box {
height: 3000px;
}
<div id="scrollprogress"></div>
<div class="backgr"></div>
<div class="box"></div>
You can approximate this using negative box shadow:
body::-webkit-scrollbar {
width: 1em;
}
body::-webkit-scrollbar-thumb {
background-color: orange;
box-shadow:-1000vmax -1000vmax 0px 1000vmax orange;
}
body {
width:300vw;
height:300vh;
background:linear-gradient(60deg,red,blue,orange);
margin:0;
}
html {
background:#fff;
}

React Carousel - images with different widths display wrong

I have an array of URLs that I am passing in as a prop to a slide component. Only one URL is passed at a time using a stateful index. Whenever the right button is clicked, the index is incremented by 1 and vice versa for the left. This is working to display the next URL in the array; however, the images seem to repeat even though I set no-repeat on the background. This is due to the images being different widths and some being smaller than the last. I've tried setting the parent container to a set width so that the slide class is 100% the width of the parent but this doesn't seem to be working for me. Below are the two components that I'm using and the styles file.
//Parent carousel component
const HeroImage = ({state}) => {
const [index, setIndex] = useState(0);
var imgUrls = [];
const goToPrevSlide = () => {
setIndex(index - 1);
}
const goToNextSlide = () => {
setIndex(index + 1);
}
return(
<React.Fragment>
<StyledHeroImage>
<div className="slider-wrapper">
{state.heroImage.map(image => {
imgUrls.push(`${IMAGE_BASE_URL}${BACKDROP_SIZE}${image.backdrop_path}`);
})}
<Slide title={state.heroImage.original_title} text={state.heroImage.overview} image={imgUrls[index]}/>
</div>
<LeftArrow goToPrevSlide={goToPrevSlide}/>
<RightArrow goToNextSlide={goToNextSlide}/>
</StyledHeroImage>
</React.Fragment>
)
}
//child Slide Component
const Slide = ({image, text, title}) => {
const styles = {
background: `linear-gradient(to bottom, rgba(0,0,0,0) 39%, rgba(0,0,0,0) 41%, rgba(0,0,0,0.65) 100%), url(${image})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
}
return(
<div className="slide" style={styles}>
<div className="heroimage-content">
<div className="heroimage-text">
<h1>{title}</h1>
<p>{text}</p>
</div>
</div>
</div>
)
}
//Styles file
export const StyledHeroImage = styled.div`
height: 650px;
width: 100%;
margin: 0 auto;
.slider-wrapper{
height: 100%;
width: 100%;
}
.slide{
width: 100%;
height: 100%;
}
.arrow{
height: 50px;
width: 50px;
display: flex;
align-items: center;
justify-content: center;
background: #f9f9f9;
border-radius: 50%;
cursor: pointer;
transition: transform ease-in .1s;
}
.nextArrow {
position: absolute;
top: 50%;
right: 25px;
z-index: 999;
color: #fff;
}
.backArrow {
position: absolute;
top: 50%;
left: 25px;
z-index: 999;
color: #fff;
}
.fa-arrow-right:before, .fa-arrow-left:before {
color: #222
}
`;

CSS square with dynamic height

I need to make a div square. The height of the div is dynamically changing and I want the width to be equal to the height. I have seen a lot of solutions to set the height to be equal to the width (with padding-bottom on pseudo-elements), but I need it the other way arround. Is this possible with pure CSS?
No .... well, there is this trick, where one use a hidden image
div {
display: inline-block;
height: 170px;
background: red;
}
div img {
visibility: hidden;
height: 100%;
}
<div>
<img src="http://placehold.it/50">
</div>
Updated
And here is a script version, that also keep it within the width
Stack snippet
(function (d,t) {
window.addEventListener("resize", throttler, false);
window.addEventListener("load", throttler(), false); /* run once on load to init */
function throttler() {
if ( !t ) {
t = setTimeout(function() {
t = null;
keepSquared(d.querySelector('.container'),
d.querySelector('.squared'));
}, 66);
}
}
function keepSquared(co,el) {
var s = window.getComputedStyle(co, null);
var m = Math.min(
parseFloat(s.getPropertyValue("width")),
parseFloat(s.getPropertyValue("height")));
el.style.cssText =
'width: ' + m + 'px; height: ' + m + 'px;';
}
})(document,null);
html, body {
height: 100%;
margin: 0;
}
.container {
position: relative;
width: 50%;
height: 50%;
top: 50px;
left: 50px;
border: 1px solid black;
box-sizing: border-box;
}
.squared {
background-color: red;
}
<div class="container">
<div class="squared">
</div>
</div>
Note: Since resize events can fire at a high rate, the throttler is used to reduced the rate so the handler doesn't execute expensive operations such as DOM modifications too often.

Resources