I am trying to create a foldable menu, through CSS and JS. Css works (almost) correctly (the menu folds and unfolds, even if the class is not correctly applied) but not the JS code, which should change the innerText of the <li> acting as a button from ">" to "<" and opposite.
I have been messing around with js code for a while (making sure that document.getElementById is not undefined), but neither element.innerText or element.innerHTML seem to work properly.
I have two questions:
When applying an animation, and having two classes, shouldn't respect both classes (I mean, the navbar should be red)? Should I add nav class AFTER the animation is done, or fill the navbar in red color through the animation?
Why does ignore InnerText/InnerHTML instructions?? I have debugged the code and definitely goes through that instruction and I cannot understand why the change is not done...
var navButton;
var navbar;
const init = ()=>{
navbar = document.getElementById("navbar");
navButton = document.getElementById("foldButton");
navButton.addEventListener('click', function(){
if(navbar.className==="init nav" || navbar.className==="fade-out-right nav"){
navButton.InnerText=`<p><</p>`;
toggleFold();
}
else{
navButton.InnerText=`<p>></p>`;
toggleFold();
}
});
}
const toggleFold = () => {
if(navbar.className==="init nav" || navbar.className==="fade-out-right nav"){
navbar.className="fade-in-left nav";
}else{
navbar.className="fade-out-right nav";
}
};
* {
margin: 0;
padding: 0;
}
html {
box-sizing: border-box;
font-size: 62.5%;
scroll-behavior: smooth;
}
/* Base styles */
.nav {
display: flex;
justify-content: flex-end;
position: fixed;
top: 0;
left: 0;
width: 4%;
background: red;
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.4);
z-index: 10;
}
.nav-list {
display: flex;
margin-right: 2rem;
list-style: none;
}
.nav-list li {
display: block;
font-size: 2.2rem;
padding: 2rem;
}
.nav-list a{
color:black
}
.nav-list a:hover {
background: blue;
}
.fade-in-left {
animation-name: fade-in-left;
animation-duration: 2s;
animation-timing-function: ease;
animation-fill-mode: forwards;
}
#keyframes fade-in-left {
from {
opacity: 1;
transform: translateX(-4%);
}
to {
opacity: 1;
transform: translateX(305px);
}
}
.fade-out-right {
animation-name: fade-out-right;
animation-duration: 2s;
animation-timing-function: ease;
animation-fill-mode: forwards;
}
#keyframes fade-out-right {
from {
opacity: 1;
transform: translateX(305px);
}
to {
opacity: 1;
transform: translateX(-4%);
}
}
<body onload="init()">
<nav id="navbar" class="init nav">
<ul class='nav-list'>
<li><a href='#welcome-section'>About</a></li>
<li><a href='#projects'>Work</a></li>
<li><a href='#contact'>Contact</a></li>
<li id="foldButton"><p>></p></li>
</ul>
</nav>
</body>
Thank you for helping me out.
Apart from my low IQ for the bad typo, whenever the color is not filled when using an animation, use internal container instead (in my case, I filled ul instead of navbar).
Related
CodePen
I am trying to replicate the letter flipping animation in Wordle. But I cannot manage the smooth chaining/sequencing. How can I fix it? (I guess I need to use the JS Promise feature, but yet to understand that concept.)
function myFunction() {
var tiles = document.getElementsByClassName("inner");
var myArray = Array.from(tiles);
myArray.map(function (tile) {
tile.classList.add("flip-in");
// tile.style.setProperty("--flipColor", "green");
tile.addEventListener(
"animationend",
() => {
tile.classList.remove("flip-in");
tile.style.backgroundColor = "green";
tile.classList.add("flip-out");
},
{
once: true
}
);
return;
});
}
var flipper = document.getElementById("flipper");
flipper.addEventListener("click", myFunction);
You've essentially done it but probably overengineered it a bit. The wordle animation is pretty simple to accomplish using only one animation.
First, let's take care of the CSS animation. Since we will only use one animation for the entire flip we can rename it "flip".
To simulate the card "flipping" we can adjust the scale on the height rather than flipping it. At the same time, we can also apply the background color change.
We can also remove the animation-delay styles. We will apply these dynamically in the JS.
#keyframes flip {
0% {
transform: scaleY(1);
}
50% {
background: white;
transform: scaleY(0);
}
100% {
transform: scaleY(1);
background: green;
}
}
We have to mark the animation as fill-mode: forwards
.flip {
animation: flip 500ms ease forwards;
}
Next, we can simplify the JS to only apply the class. Do some renaming to easier understand what everyone is and does. And here we can also dynamically apply the animation delay based on the index of the tile. This way we will support all different number of tiles.
function applyFlip() {
var tiles = document.getElementsByClassName("inner");
var tilesArray = Array.from(tiles);
tilesArray.map(function (tile, i) {
tile.classList.add("flip");
tile.style.animationDelay = `${i * 100}ms`;
});
}
var flipper = document.getElementById("flipper");
flipper.addEventListener("click", applyFlip);
Here's a working snippet:
function applyFlip() {
var tiles = document.getElementsByClassName("inner");
var tilesArray = Array.from(tiles);
tilesArray.map(function (tile, i) {
tile.classList.add("flip");
tile.style.animationDelay = `${i * 100}ms`;
});
}
var flipper = document.getElementById("flipper");
flipper.addEventListener("click", applyFlip);
.container {
width: 540px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 2px;
}
.inner {
display: flex;
justify-content: center;
align-items: center;
font-size: 40px;
color: black;
width: 100px;
height: 100px;
background: white;
border: 2px solid black;
border-radius: 6px;
}
button {
margin: 50px;
font-size: 24px;
padding: 4px;
}
.flip {
animation: flip 500ms ease forwards;
}
#keyframes flip {
0% {
transform: scaleY(1);
}
50% {
background: white;
transform: scaleY(0);
}
100% {
transform: scaleY(1);
background: green;
}
}
<div class="container">
<div class="inner">S</div>
<div class="inner">T</div>
<div class="inner">A</div>
<div class="inner">C</div>
<div class="inner">K</div>
</div>
<button id="flipper"> Flipper </button>
Hello and thank you in advance for reading my question.
GOAL: Set image so that once it's scrolled into view it transitions smoothly into a set position - but still reacts to :hover. Using #keyframes and a little JavaScript, I set the image to opacity: 0 and it's final opacity to opacity: .85. Then I added a hover effect in CSS to make it's opacity: 1
The issue is once it's finished with it's transition - it disappears - reverting to it's original opacity which is zero. I managed to make it freeze at .85 with animation-fill-mode: forwards, rather than animation-fill-mode: none, but then it won't respond to :hover
And here's a test snippet of the problem in action:
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
}
/* APPEND-CHILD */
.shift_frame_center_img {
animation: center_img 1s 0.5s none;
}
/* CHILD ON HOVER */
.features-img-wrapper img:hover {
opacity: 1;
transform: scale(1.035);
}
/* KEYFRAMES */
#keyframes center_img {
0% {
transform: translateY(20rem);
}
100% {
transform: translateY(0);
opacity: .85;
}
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
If I could get a hand with this that would be wonderful, I'm a bit of a beginner and have already spent a few hours on this, all feedback welcome. Thank you very much.
Solution 1
To understand why the hover effect was not working with the animation-fill-mode: forwards, read this answer.
You can fix that by adding !important property to the hover styles:
.features-img-wrapper img:hover {
opacity: 1 !important;
transform: scale(1.035) !important;
}
The problem, in this case, is that the transition will not work for hover.
Solution 2
You could remove the animation entirely and add the final state styles to the shift_frame_center_img class.
But you would still need to use the !important property because of the CSS Specificity.
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
transform: translateY(20rem);
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
}
/* APPEND-CHILD */
.shift_frame_center_img {
transform: none !important;
opacity: .85 !important;
}
/* CHILD ON HOVER */
.features-img-wrapper img:hover {
opacity: 1 !important;
transform: scale(1.035) !important;
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
This snippet removes the need for fill-mode forwards by setting the img to have opacity 1 as its initial state so it will revert to that at the end of the animation.
The animation itself is altered to take 1.5s rather than 1s with the first third simply setting the img opacity to 0 so it can't be seen. This gives the delay effect.
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
opacity: 1;
}
/* APPEND-CHILD */
.features-img-wrapper img {
animation: center_img 1.5s 0s none;
}
/* CHILD ON HOVER */
.shift_frame_center_img:hover {
opacity: 1;
transform: translateY(0) scale(1.035);
}
/* KEYFRAMES */
#keyframes center_img {
0% {
transform: translateY(20rem) scale(1);
opacity: 0;
}
33.33% {
transform: translateY(20rem) scale(1);
opacity: 0;
}
100% {
transform: translateY(0) scale(1);
opacity: .85;
}
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
Note: as each transform setting will reset anything that isn't included both tranlateY and scale are included in each setting.
Outside the SO snippet system it was possible to leave the animation settings untouched by chaining another animation to the front which ran for 0.5s and just set the img to opacity: 0. This did not work in the snippet system (it got into a loop of flashing on and off) hence the introduction of one but extended animation.
I want to create a simple line through animation and so far I'm here:
.strikethrough {
display: inline-block;
position: relative;
line-height: 1.5em;
}
.strikethrough:after {
content: '';
position: absolute;
display: block;
width: 100%;
height: 1px;
box-shadow: 0 1px rgba(252, 3, 3,0.7);
margin-top: -0.7em;
background: rgba(252, 3, 3,0.8);
transform-origin: center left;
animation: strikethrough 1s 0.5s cubic-bezier(.55, 0, .1, 1) 1;
}
#keyframes strikethrough {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
<span class="strikethrough">Favor packaging over toy</span>
As you see everything works fine except two things:
Now we can see the line at first then it hides and starts the animation, I want to see only the animated line.
I want to initiate the animation using javascript... but with this pseudo-element (after) it seems complicated!
You can fix your animation using animation-fill-mode:
animation-fill-mode: backwards;
To trigger your animation, just add the strikethrough class. The thing where I think this won't work is when you have a multiline text, as your ::after won't cover that.
document.querySelector( '.strikethrough' ).addEventListener( 'click', event => { event.target.classList.toggle( 'strikethrough' ); });
.strikethrough {
display: inline-block;
position: relative;
line-height: 1.5em;
}
.strikethrough:after {
content: '';
position: absolute;
display: block;
width: 100%;
height: 1px;
box-shadow: 0 1px rgba(252, 3, 3,0.7);
margin-top: -0.7em;
background: rgba(252, 3, 3,0.8);
transform-origin: center left;
animation: strikethrough 1s 0.5s cubic-bezier(.55, 0, .1, 1) 1;
animation-fill-mode: backwards;
}
#keyframes strikethrough {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
<span class="strikethrough">Favor packaging over toy</span>
Personally, I would take this simpler tack to reduce the amount of actual objects on screen, and the amount of code, by using a background image to scale. If cleverly set up, you could even multi-line this (by making the background the line height and having a middle pixel in it - and with SVGs you could ensure it was only 1px regardless of the stretching etc...).
document.querySelector( 'p' ).addEventListener( 'click', event => {
event.target.classList.toggle( 'strikethrough' );
})
#keyframes stretch {
to { background-size: 100% var(--line-height); }
}
:root {
--line-height: 1.2em;
}
p {
line-height: var(--line-height);
}
.strikethrough {
line-height: 1.2em;
background: url('data:image/svg+xml;charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none"><line x1="0" y1="50%" x2="100%" y2="50%" stroke="black" stroke-width="1px" /></svg>') repeat-y 0 0 / 0 var(--line-height);
animation: stretch 4s;
animation-fill-mode: forwards;
}
<p>Hello World!<br />Another line, does it work?</p>
I have a simple app that outputs messages when some actions are performed on a page, and it is done through the javascript createElement function, what I want to do is add a special style to only the newest message, so if a newer messages comes up the old newest message would revert to the old style. Is there any way to do this? when I createElement it seems all the divs would have to have the same class, and anything I try just applies the style to all the messages.
So is there anything to use in CSS that allows me to only apply a style to the newest member of a certain class?
here is how I create the new messages
function selfMsg(message) {
const msg = document.createElement('div');
msg.style.cssText = 'display: flex; justify-content: flex-end;background-color:aquamarine';
msg.innerText = message;
display.append(msg);
}
but all this does is style all the divs the same style, and I have no idea how I'm supposed to remove the style if a newer message comes up.
Ideally I'm looking for something in CSS that I can use in my stylesheet file that can target an entire class like how ".classnamehere" works, but only applies the style to the newest member of that class.
Depending on the container your divs are in, you can use CSS last:child (https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child) like this:
So if your container has the class display, you'd do
.display div:last-child {
display: flex;
justify-content: flex-end;
background-color:aquamarine
}
In case you don't know how to use this outside of javascript, simply wrap the above code in a style tag and add it to the head of your html document.
For clarity and possibly-upcoming-changes reasons, i highly suggest giving all of your divs a class on creation and using that instead of just div in the CSS. It's just easier to maintain that way.
.message-wrapper .message:last-child {
color: red;
}
<div class="message-wrapper">
<div class="message">Message 1</div>
<div class="message">Message 2</div>
<div class="message">Message 3</div>
</div>
You can use the :last-child pseudo selector. Something like:
.display div:last-child{
// your style
}
You can add a class to your messages like this:
msg.classList.add("mystyle");
Then use a css selector to only apply styles to the last element of this type
div.mystyle:last-of-type {
/* ... your styles */
}
Here's an example you can play with, from w3schools: https://www.w3schools.com/cssref/tryit.asp?filename=trycss3_last-of-type
If you want to use pure javascript, you could use element.removeAttribute("style") or element.style.cssText = null, but you would have to keep track of the previous message elements, or get ALL messages and loop through them, removing the style from all before adding the newest element. I'd recommend just using CSS.
Something along this line
The key is the :last-child pseudo class
"use strict";
console.clear();
document.getElementById('add').addEventListener('click', e => {
const target = document.getElementById('add-target')
const div = document.createElement('div')
const text = document.createTextNode(`#${target.querySelectorAll('div').length+1}`)
div.appendChild(text)
target.appendChild(div)
})
.container {
width: 600px;
margin: auto;
border: 1px solid gray;
}
.container > div {
--bg-in: lightgray;
--bg-out: transparent;
transition: background-color 1s;
padding: 20px;
font-family: sans-serif;
}
.container > div:not(:last-child) {
border-bottom: 1px solid gray;
-webkit-animation: anim-out 1s .3s both;
animation: anim-out 1s .3s both;
}
.container > div:last-child {
-webkit-animation: anim-in 1s both;
animation: anim-in 1s both;
}
#-webkit-keyframes anim-in {
from {
background-color: var(--bg-out, white);
}
to {
background-color: var(--bg-in, pink);
}
}
#keyframes anim-in {
from {
background-color: var(--bg-out, white);
}
to {
background-color: var(--bg-in, pink);
}
}
#-webkit-keyframes anim-out {
from {
background-color: var(--bg-in, pink);
}
to {
background-color: var(--bg-out, white);
}
}
#keyframes anim-out {
from {
background-color: var(--bg-in, pink);
}
to {
background-color: var(--bg-out, white);
}
}
<button id="add">Add</button>
<div class="container" id="add-target">
<div>#1</div>
<div>#2</div>
<div>#3</div>
</div>
Or—a little bit more sophisticated—with animationend event to implement a fadeoutafter some time
"use strict";
console.clear();
document.getElementById('add').addEventListener('click', e => {
const target = document.getElementById('add-target')
const div = document.createElement('div')
div.classList.add('in')
const text = document.createTextNode(`#${target.querySelectorAll('div').length+1}`)
div.appendChild(text)
target.appendChild(div)
})
document.addEventListener('animationend', e => {
e.target.classList.add('out');
e.target.classList.remove('in');
})
.container {
width: 600px;
margin: auto;
border: 1px solid gray;
}
.container > div {
--bg-in: lightgray;
--bg-peak: gray;
--bg-out: transparent;
transition: background-color 1s;
padding: 20px;
font-family: sans-serif;
}
.container > div:not(:last-child) {
border-bottom: 1px solid gray;
}
.container > div.out {
-webkit-animation: anim-out 1s 5s both ease-out;
animation: anim-out 1s 5s both ease-out;
}
.container > div.in {
-webkit-animation: anim-in 1s both;
animation: anim-in 1s both;
}
#-webkit-keyframes anim-in {
0% {
background-color: var(--bg-out, white);
}
25% {
background-color: var(--bg-peak, black);
}
100% {
background-color: var(--bg-in, pink);
}
}
#keyframes anim-in {
0% {
background-color: var(--bg-out, white);
}
25% {
background-color: var(--bg-peak, black);
}
100% {
background-color: var(--bg-in, pink);
}
}
#-webkit-keyframes anim-out {
from {
background-color: var(--bg-in, pink);
}
to {
background-color: var(--bg-out, white);
}
}
#keyframes anim-out {
from {
background-color: var(--bg-in, pink);
}
to {
background-color: var(--bg-out, white);
}
}
<button id="add">Add</button>
<div class="container" id="add-target">
<div>#1</div>
<div>#2</div>
<div>#3</div>
</div>
I'm trying to make a slide menu but I don't know how would I close it back.
Basically i'm trying to do something like this website https://zero.nyc/
const menu = document.querySelector('aside');
const nav = document.querySelector('nav');
menu.addEventListener('click', () => {
nav.style.marginLeft = '0';
menu.style.left = '97vw';
})
aside {
border-right: 1px solid #e2e2e2;
width: 3vw;
height: 100vh;
line-height: 100vh;
position: fixed;
top: 0;
left: 0;
transition: 1s;
}
nav {
width: 97vw;
height: 100vh;
margin-left: -100vw;
transition: 1s;
}
<aside>
menu
</aside>
<nav>
<ul>
<li>Home</li>
<li>ABOUT</li>
<li>Contact</li>
</ul>
</nav>
Use css classes and toggle them on or off on in your click method instead of setting the styles through javascript.
Snippet:
const menu = document.querySelector('aside');
const nav = document.querySelector('nav');
menu.addEventListener('click', () => {
nav.classList.toggle("margin");
menu.classList.toggle("left");
})
aside {
border-right: 1px solid #e2e2e2;
width: 3vw;
height: 100vh;
line-height: 100vh;
position: fixed;
top: 0;
left: 0;
transition: 1s;
-webkit-transition: 1s;
-ms-transition: 1s;
-moz-transition: 1s;
}
nav {
width: 97vw;
height: 100vh;
margin-left: -100vw;
transition: 1s;
-webkit-transition: 1s;
-ms-transition: 1s;
-moz-transition: 1s;
}
.margin {
margin-left: 0;
}
.left {
left: 97vw;
}
<aside>
menu
</aside>
<nav>
<ul>
<li>Home</li>
<li>ABOUT</li>
<li>Contact</li>
</ul>
</nav>
I would create css classes to toggle within your click event handler instead of manually changing the styles.
On click you're basically going to:
Grab the elements you want to change
Toggle the classes using classList.toggle (if you're not worried about < ie9)
Benefit of using classes is once you remove it, the element simply reverts. It's also more maintainable because you don't have to keep track of the styles you change.
Use transform: translateX() instead of margin. You also have to write some state of your menu i.e. opened/closed. You can use a css class for that, and in your eventListener you can check if menu is opened, then onClick animate from translateX(0) to translateX(-100%)and remove opened class.
Or you can write styles with transitions, or #keyframes animations, everything will work. Just build the idea of storing opened state first.