I have a custom web component that I am trying to make which looks like this
But it looks like this after I reload the page
Notice the thin white div at the very bottom of each image.
I notice that I get the desired result (the fist image) immediately following a Webpack build triggered by saving this component's javascript file. Each time I reload the page, the second image is rendered on screen. What causes this behavior? I will attach the pertinent parts of my code below. (The styling for the large yellow and white components is done for privacy)
FeaturedProjectCard.js
const template = document.createElement('template')
template.innerHTML = `
<style>
.bottomDivider {
position: absolute;
bottom: 0;
width: 75%;
height: 1px;
margin: 0;
background-color: white;
}
.container {
position: relative;
width: 1000px;
isolation: isolate;
}
.description {
border-radius: 10px;
padding: 25px;
color: white;
background-color: white;
}
.linksContainer {
display: flex;
}
.linksContainer > a {
width: 20px;
padding: 10px;
color: white;
transition: color 0.5s;
}
.linksContainer > a:first-child {
padding-left: 0;
}
.linksContainer > a:hover {
color: yellow;
}
.projDesc {
width: 50%;
display: flex;
flex-direction: column;
pointer-events: none;
}
.projDesc > * {
width: fit-content;
pointer-events: auto;
}
.projImage {
position: absolute;
top: 50%;
right: 0;
height: 300px;
transform: translateY(-50%);
background-color: yellow;
border-radius: 5px;
z-index: -1;
}
.projImage > img {
height: inherit;
border-radius: inherit;
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0;
transition: opacity 0.5s, filter 0.5s, -webkit-filter 0.5s;
}
.projImage > img:hover {
opacity: 1;
filter: grayscale(0%);
-webkit-filter: grayscale(0%);
}
.projectHeading {
margin: 10px 0;
color: yellow;
font-size: 12pt;
}
.projectName {
display: inline;
margin: 15px 0;
font-size: 21pt;
color: white;
}
.softwareContainer {
margin: 25px 0 10px 0;
color: white;
}
.softwareContainer > span:not(:last-child) {
padding-right: 30px;
}
</style>
<div class="container">
<div class="projDesc">
<span class="projectHeading">Recent Project</span>
<span class="projectName"></span>
<div class="description"></div>
<div class="softwareContainer"></div>
<div class="linksContainer"></div>
</div>
<div class="projImage">
<img>
</div>
<p class="bottomDivider"></p>
</div>
`
class FeaturedProjectCard extends HTMLElement {
constructor() {
super()
//Enable shadow DOM
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(template.content.cloneNode(true))
//Insert project image
this.shadowRoot.querySelector('img').src = this.getAttribute('image')
//Insert project heading
this.shadowRoot.querySelector('.projectName').textContent = this.getAttribute('heading')
//Insert project description
this.shadowRoot.querySelector('.description').innerHTML = //Get description text
//Insert software array
const softwareItems = //Get items
if(softwareItems) {
const softwareContainer = this.shadowRoot.querySelector('.softwareContainer')
for(let i = 0; i < softwareItems.length; i++)
softwareContainer.innerHTML += `<span>${softwareItems[i].textContent}</span>`
}
//Insert link array
const links = //Get links
if(links) {
const linksContainer = this.shadowRoot.querySelector('.linksContainer')
for(let i = 0; i < links.length; i++) {
linksContainer.innerHTML += `<a target="_blank" href="${links[i].getAttribute('target')}">${links[i].innerHTML}</a>`
}
}
//Handle reverse attribute
if(this.hasAttribute('reversed')) {
this.shadowRoot.querySelector('.bottomDivider').style.right = 0;
this.shadowRoot.lastElementChild.style.height = `${this.shadowRoot.lastElementChild.offsetHeight}px`
const projDesc = this.shadowRoot.querySelector('.projDesc')
const projImage = this.shadowRoot.querySelector('.projImage')
const softwareItems = Array.from(this.shadowRoot.querySelectorAll('.softwareContainer > span'))
projDesc.style.position = 'absolute'
projDesc.style.right = '0'
projDesc.style.left = 'auto'
projImage.style.left = '0'
projImage.style.right = 'auto'
projDesc.style.alignItems = 'flex-end'
softwareItems.splice(0, 1)[0].style.padding = '0'
const padding = getComputedStyle(softwareItems[0]).getPropertyValue('padding-right')
softwareItems.forEach(item => {
item.style.paddingLeft = padding
item.style.paddingRight = '0'
})
}
//Clear markup
this.innerHTML = null
}
}
window.customElements.define('featured-project-card', FeaturedProjectCard)
I use these component in HTML with the following syntax.
<featured-project-card class="" reversed image="/something.png" heading="Heading">
I think that the root cause of this behavior is the inclusion of the optional 'reversed' attribute. The other components that don't use this attribute are not affected by this issue since div.projDesc has position: static for those instances. This allows the div.container to resize. These images show the only component where this attribute is used. I will end this post with one final image of a component without the 'reversed' attribute for reference. In essence, I achieve the reverse effect by setting the div.projDesc properties position, right, and left to absolute, 0, and auto respectively. Consequently, I had to set the div.container height equal to what it was prior to these changes. Otherwise the element would not size correcly since position: absolute is being used on the only child with position: static
I used a ResizeObserver to see if the div.projDesc was resizing itself more than once and indeed it was. To wrap up, the 'reversed' div.projDesc is resizing itself strangely (likely because of how the 'reverse' attribute is handled) as opposed to non 'reversed' elements. I am aware that there may be an easier way to solve this problem and I am open to suggestions, however, I would like to pin down the source of this behavior in hopes of gaining a better understanding of the nuances of HTML, CSS, & JS. I am more than happy to clarify myself for anyone that is willing to lend a hand. Thank you in advance!
Non reversed component
Related
I have a small scroll effect which simulate that a logo will disappear if a lower div will scroll over it.
Currently I'm checking if two divs are intersecting. If this is true, then the height of the div of the logo will decrease with the scroll position of the div beneath.
Unfortunately, my demo is not foolproof and some fragments of the logo are still visible.
Is there a way to do this jank-free? Maybe with requestAnimationFrame?
function elementsOverlap(el1, el2) {
const domRect1 = el1.getBoundingClientRect();
const domRect2 = el2.getBoundingClientRect();
return !(
domRect1.top > domRect2.bottom ||
domRect1.right < domRect2.left ||
domRect1.bottom < domRect2.top ||
domRect1.left > domRect2.right
);
}
const el1 = document.querySelector(".logo");
const el2 = document.querySelector(".clickblocks");
let scrollPositionEl2;
let heightDifference;
const logoHeight = el1.offsetHeight;
document.addEventListener("DOMContentLoaded", () => {
var scrollDirectionDown;
scrollDirectionDown = true;
window.addEventListener("scroll", () => {
if (this.oldScroll > this.scrollY) {
scrollDirectionDown = false;
} else {
scrollDirectionDown = true;
}
this.oldScroll = this.scrollY;
// test
if (scrollDirectionDown) {
if (elementsOverlap(el1, el2) === true) {
scrollPositionEl2 = el2.getBoundingClientRect().top;
heightDifference = logoHeight - scrollPositionEl2 + 100;
//console.log(logoHeight - heightDifference);
el1.style.height = `${logoHeight - heightDifference}px`;
}
} else {
//scrolling up
scrollPositionEl2 = el2.getBoundingClientRect().top - 100;
el1.style.height = `${scrollPositionEl2}px`;
//console.log(logoHeight);
}
});
});
#import url("https://fonts.googleapis.com/css2?family=Inter:wght#900&display=swap");
.wrapper {
max-width: 100vw;
margin: 0 auto;
background-image: url("https://picsum.photos/1920/1080");
background-size: cover;
background-attachment: fixed;
height: 1200px;
position: relative;
&::after {
content: "";
position: absolute;
background: rgba(0, 0, 0, 0.3);
width: 100%;
height: 100%;
inset: 0;
}
}
body {
margin: 0;
}
main {
width: 100%;
height: 100vh;
position: relative;
z-index: 1;
}
.clickblocks {
width: 100%;
height: 200px;
display: grid;
grid-template-columns: repeat(12, (minmax(0, 1fr)));
}
.clickblock {
transition: all ease-in-out 0.2s;
backdrop-filter: blur(0px);
border: 1px solid #fff;
height: 100%;
grid-column: span 6 / span 6;
font-size: 54px;
font-weight: 700;
padding: 24px;
font-family: "Inter", sans-serif;
color: white;
text-transform: uppercase;
&:hover {
backdrop-filter: blur(10px);
}
}
.logo {
background: url("https://svgshare.com/i/ivR.svg");
width: 100%;
background-repeat: no-repeat;
background-position: top;
position: fixed;
top: 100px;
}
.logo-wrapper {
position: relative;
}
<div class="wrapper">
<main>
<div class="logo-wrapper" style="height: 390px">
<div class="logo" style="height: 300px">
</div>
</div>
<div class="clickblocks">
<div class="clickblock">
Some Content
</div>
</div>
</main>
</div>
Few things here to optimize your performance.
getBoundingClientRect() is a rather expensive calculation. If there are NO other options it's fine.
The Intersection Observer API is a lot more performant, and you can set the root element on the API. Then observe the element that is moving. This should be able to telly you if their are colliding.
Whenever you do scroll based logic, you should really try and throttle the logic so that the scroll any fires ever 16.6ms. That will reduce the number of times the calculations are made, and speed things up on the FE.
Learn how to use Google Chrome's performance tab. It can be overwhelming at first, but it gives you the ability to drill into the exact piece of code that's slowing your site down.
Learn about JS's event loop, and what's really going on under the hood. This video by Jake Archibald really help me understand it.
Hope this helped, sorry that I didn't give you an actual solution.
THE WHOLE CODE IN JSFIDDLE
I have been struggling to effectively remove the code and css created in the function Seifenblasen_blasen()
function Seifenblasen_blasen(){
btn1.style.display = 'none';
document.getElementById("text").innerHTML="Bubble"
const section = document.querySelector('section')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
},8000)
}
const Blaseninterval = setInterval(Seifenblasen_blasen, 100)
created CSS:
section {
width: 100%;
height: 100vh;
overflow: hidden;
background: #1F69FA;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
section.text{
font-size: 10em;
color: #333;
margin: 0 auto;
text-align: center;
font-family: consolas;
background-color:#1F69FA;
pointer-events: none;
border: none;
}
section spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 4s linear infinite;
}
section spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
section span {
margin-top: 700px;
font-size: 1em;
color: #333;
margin: 0 auto;
font-family: consolas;
background-color: #1F69FA;
border: none;
position: absolute;
}
HTML:
<section id="section">
<div class="content">
<button id="btn"></button>
<button id="btn1"></button>
</div>
</section>
to then execute the next function function next(). This removal is needed because when I don't remove the elements from the first function the second wont work. I could just do document.head.innerHTML = "" but that would then also remove the css needed for the button appearing in the next function. So then I tried to make variables with const
const btn = document.getElementById('text');
const btn1 = document.getElementById('text1');
const section = document.querySelector('section')
// in function Seifenblasen_blasen()
btn1.style.display = 'none';
// in function next()
section.style.display = 'none';
btn.style.display = 'none';
btn1.style.display = 'block';
to hide and show only parts of the css without removing the css entirely to keep the styling intact, but now nothing works anymore.(the button on the next Screen doesn't show up at all and the first button does not contain any styling) My endgoal is that I can essentially switch between two screens one showing the bubbles and one the bouncy balls and when I click on the button it goes on. (for example start is bubbles. I click -> Bounce, click again -> back to Bubbles and so on)
I'm new to coding, and I'm trying to learn the basics. I wanted to practice what I learned by making flashcards (nothing complicated like saving it, importing it, or exporting it). So far, I made a table that the user can edit. I know how to gather data from the table, but I don't know how to make a CSS flashcard appear every time the user adds a card to the table. I am aware that the code will not work since I put the CSS in JavaScript since this code is just meant to show what I am trying to do. Also, if I am taking a completely wrong approach, please let me know. Thank you! Please excuse the poor variable naming, I was just testing some things.
<script>
function getFlashcardValue() {
for (var repeat = 0; repeat < 200; repeat++) {
var Table = document.getElementById('flashcardsTable');
var column1 = 0;
var column2 = 1;
var numberOfFlashcards = 2;
for (var row = 0; row < numberOfFlashcards; row++) {
var Cells = Table.rows.item(1).cells;
var Question1 = Cells.item(column1).innerHTML;
var Cells1 = Table.rows.item(1).cells;
var Answer1 = Cells.item(column2).innerHTML;
document.getElementById("myFlashcardQuestion" + row).innerHTML = Question1;
document.getElementById("myFlashcardAnswer" + row).innerHTML = Answer1;
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
<span id="myFlashcardQuestion1"></span>
</div>
<div class="flip-card-back">
<span id="myFlashcardAnswer1"></span>
</div>
</div>
</div>
}
}
}
</script>
<p style = "font-size: 25px">Hover over the flashcard to flip it!</p>
<style>
.flip-card {
background-color: transparent;
width: 350px;
height: 175px;
margin: auto;
padding: 5px 5px;
perspective: 1000px;
}
.flip-card-inner {
position: relative;
background-color: lightblue;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.6s;
transform-style: preserve-3d;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front, .flip-card-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.flip-card-front {
background-color: lightblue;
width: 350px;
height: 175px;
color: black;
font-size: 35px;
text-alignment: center;
}
.flip-card-back {
background-color: red;
color: white;
font-size: 35px;
text-alignment: center;
transform: rotateY(180deg);
}
</style>
So first of all you can create a code snippet in stackoverflows editor (see below), or use jsfiddle and post a shared-link.
It depends on which action the user has to do after he enters the data.
If it is, for example, a button click, then it is possible to call a function that shows the user's input in the flashcard. Now if you want that for every single Q&A you have to create Elements in the for loop and edit them there. Here a little example.
var allCards = document.getElementById("allCards");
for (var i = 1; i <= 5; i++) { //i used 5, you should use length of data
var question = document.createElement("div");
question.textContent = "Question " + i;
question.classList.add("flip-card");
allCards.appendChild(question);
}
.flip-card {
background-color: lightblue;
width: 350px;
height: 175px;
margin: 10px auto;
padding: 5px 5px;
font-size: 35px;
text-align: center;
}
<div id="allCards"></div>
Edit:
As promised, here is an example of how you can set up the flip cards.
https://jsfiddle.net/ybu59hfp/1/
Your concern should now be resolved. If you have any further questions, feel free to write to me in the chat or read a little about JavaScript on the Internet.
I REALLY want to achieve 60fps animations. Specifically, on my input elements on the focus event. The highest (according to Chrome Developer Tools) fps I can achieve is about 18fps.
I am using everything that is said to be best practice: window.requestAnimationFrame in my JavaScript, will-change in my css, and i'm using transforms for the actual animations. Even with all of these set in place, my animations aren't near 60fps.
var form = {};
function addActive(element) {
element.highlightElement.classList.add("input-highlight-active");
}
function removeActive(element) {
element.highlightElement.classList.remove("input-highlight-active");
}
// loops through the entire object removing "active" class on all elements except fo rthe element that is the target.
function setNewState(element, id, obj) {
for (var key in obj) {
if (id === key) {
window.requestAnimationFrame(() => addActive(element));
} else {
removeActive(obj[key]);
}
}
}
// Captures the event.target.id and corresponding object to be passed to setNewState function
function handleEvent(e) {
var active = form[e.target.id];
var activeId = e.target.id;
setNewState(active, activeId, form);
}
// Adds focus event listener to html elements
function addEvent() {
var inputElements = document.querySelectorAll('.input-wrapper > input');
inputElements = Array.from(inputElements);
inputElements.map(el => el.addEventListener('focus', (e) => handleEvent(e)));
}
// Creates object using input id as object key
function init() {
var temp = {};
var inputElements = document.querySelectorAll('.input-wrapper > input');
for (var i = 0; i < inputElements.length; i++) {
temp[inputElements[i].id] = {
highlightElement: inputElements[i].nextElementSibling,
active: false
};
}
return temp;
}
form = init();
addEvent();
body {
background-color: #121217;
}
.input-wrapper {
position: relative;
width: 60%;
margin: 0px auto 40px;
text-align: left;
}
.input-label {
position: relative;
text-align: left;
font-size: 18px;
font-weight: 300;
letter-spacing: 0.012em;
color: #eee;
}
input {
appearance: none;
-webkit-appearance: none;
position: relative;
width: 100%;
margin: 8px auto 0;
letter-spacing: 0.012em;
font-size: 18px;
color: #fafafa;
background-color: #121217;
border: 0px;
transform-origin: left;
border-bottom: 1px solid #fafafa;
}
input:focus {
outline: none;
}
.input-highlight {
will-change: transform;
position: absolute;
top: 99%;
width: 101%;
height: 2px;
background-color: #006daa;
z-index: 1;
transform-origin: left;
transform: scaleX(0);
transition: all 0.12s linear;
}
.input-highlight-active {
will-change: transform;
transform: scaleX(1);
transition: all 0.12s linear;
}
<div class="input-wrapper">
<div class="input-label">Full name</div>
<input type="text" id="fullName">
<div class="input-highlight"></div>
</div>
At the end of the day, I am sure i'm obsessing over something most won't notice. Ultimately, I just want to understand. Even if you tell me i've done all I can.
In the code you provided, you are using CSS animations, just by applying the extra class. There's no need to call requestAnimationFrame because it's designed to be called multiple times via the callback.
Since you took advantage of CSS animations you only need to add the class that changes the scale ONCE on focus, then you can remove it on blur:
.input-highlight-active {
transform: scaleX(1);
}
Because the input already has a class where the transition value specifies it will animate all properties (that it's able to animate), you only need to specify the property change that's animated.
Demo
In the following code, when I put the div with class thumb-bar, the JavaScript I have written works but if place use it after full-img div tag, it doesn't work also the CSS attribute cursor: pointer for the thumb-bar div is not applied.
Edit - I mean the click listeners I apply using JavaScript are not working
CSS:
body {
width: 640px;
margin: 0 auto;
}
.full-img {
position: relative;
display: block;
width: 640px;
height: 480px;
}
button {
border: 0;
background: rgba(150, 150, 150, 0.6);
text-shadow: 1px 1px 1px white;
border: 1px solid #999;
position: absolute;
cursor: pointer;
top: 2px;
left: 2px;
}
.thumb-bar img {
display: block;
width: 20%;
float: left;
cursor: pointer;
}
HTML:
<div class="thumb-bar"></div>
<div class="full-img">
<img class="displayed-img" src="images/pic1.jpg">
<button class="dark">Darken</button>
</div>
JavaScript:
var displayedImage = document.querySelector('.displayed-img');
var thumbBar = document.querySelector('.thumb-bar');
btn = document.querySelector('button');
var overlay = document.querySelector('.overlay');
for (var i = 1; i <= 5; i++) {
var newImage = document.createElement('img');
newImage.setAttribute('src', 'images/pic' + i + '.jpg');
thumbBar.appendChild(newImage);
newImage.addEventListener('click', function(e) {
displayedImage.setAttribute('src', e.target.getAttribute('src'))
});
}
Because you're floating .thumb-bar img, those images are taken out of the page flow which results in the .thumb-bar element to have a height of 0, which in turn causes subsequent content to not be pushed down. That means that the .full-img element is rendered on top of the images and obscures them from the mouse pointer.
You need to clear the floats in order to get the .full-img element to render below them. This can be done by either making sure the .thumb-bar clear it's own content:
.thumb-bar {
overflow: hidden;
}
... or make the .full-img element itself clear them:
.full-img {
clear: both;
}