CSS or JavaScript for animation - css

I want to change my code so that the when my layout changes there is a sliding animation.
It is a 3 column layout and I want the left-most column to shrink a but when the user is focused on the right 2/3 of the screen.
I wrote a simple codepen demoing my code.
I am using JS to transition between two CSS classes, but I wanted to add an sliding transition to it.
I'm not sure if this should be 100% CSS or if I need to use some jQuery animation to get the desired effect.
Codepen: https://codepen.io/glennferrie/pen/vYaLpXo
const leftPane = document.getElementById('leftpane');
const centerPane = document.getElementById('centerpane');
const rightPane = document.getElementById('rightpane');
var addMode2 = function() {
leftPane.classList.add("mode-2");
rightPane.classList.add("mode-2");
centerPane.classList.add("mode-2");
}
var removeMode2 = function() {
leftPane.classList.remove("mode-2");
rightPane.classList.remove("mode-2");
centerPane.classList.remove("mode-2");
}
leftPane.addEventListener("mouseover", function(ev) {
removeMode2();
});
centerPane.addEventListener("mouseover", function(ev) {
addMode2();
});
rightPane.addEventListener("mouseover", function(ev) {
addMode2();
});
.container {
background-color: lightblue;
font-family: sans-serif;
font-size: 1.1em;
}
.left-pane,
.center-pane,
.right-pane {
padding-block-start: 10vh;
display: inline-block;
outline: dotted 2px #333;
height: 95vh;
text-align: center;
}
.left-pane {
width: 25vw;
background-color: yellow;
}
.center-pane {
width: 40vw;
}
.right-pane {
width: 33vw;
background-color: aquamarine;
}
.left-pane.mode-2 {
width: 10vw;
}
.center-pane.mode-2 {
width: 47vw;
}
.right-pane.mode-2 {
width: 40vw;
}
<div class="container">
<div class="left-pane" id="leftpane">
Left Pane Content
</div>
<div class="center-pane" id="centerpane">
Center Pane Content
</div>
<div class="right-pane" id="rightpane">
Right Pane Content
</div>
</div>

You can use a grid and animate the width of the columns. The solution below uses the has() pseudo-code which is not yet supported by Firefox.
* {
box-sizing: border-box;
}
.container {
background-color: lightblue;
font-family: sans-serif;
font-size: 1.1em;
display: grid;
/* Default setting equal width for all columns */
grid-template-columns: 2fr 2fr 2fr;
transition: grid-template-columns 0.5s ease-in-out;
}
.left-pane,
.center-pane,
.right-pane {
outline: dotted 2px #333;
height: 95vh;
text-align: center;
}
.left-pane {
background-color: yellow;
}
.right-pane {
background-color: aquamarine;
}
/* When center pane OR right pane is hovered, adjust the width of the columns of the parent */
.container:has(.center-pane:hover), .container:has(.right-pane:hover) {
grid-template-columns: 1fr 2.5fr 2.5fr;
}
<div class="container">
<div class="left-pane">
Left Pane Content
</div>
<div class="center-pane">
Center Pane Content
</div>
<div class="right-pane">
Right Pane Content
</div>
</div>

You can achieve that by adding transition property for your .left-pane, .center-pane, .right-pane elements inside CSS. It is best to have it for all three panes as they all change their width on hover.
Here's an example:
.left-pane, .center-pane, .right-pane {
padding-block-start: 10vh;
display: inline-block;
outline: dotted 2px #333;
height: 95vh;
text-align: center;
/* Transition for the width */
transition: 250ms width ease;
}
As you can see I added 3 properties for the transition.
First is time which defines how long will your animation take to complete. Second is property you want to animate, in your case it's width and the last one is the interpolator, by default it is set to linear which might be something you want but you can change it, I set it to ease to make effect more pleasing to the eyes.
You can read more about transition to learn about all the possibilities in the docs.

Related

Use a dynamically expanding div as clipping mask for background image in parent

I'm looking for a CSS solution that adapts to div contents, with the functionality of clip-path but dynamic. This is my code:
.background {
background: yellow;
text-align: center;
}
.text {
display: inline-block;
margin: 20px;
padding: 20px;
background: teal;
}
<div class="background">
<div class="text">
My text is in here
</div>
</div>
Yellow and teal are just used for illustration. I want to replace the yellow background with an image, but only show it in the teal area. The div.background spans the width of the browser, but I cannot make assumptions about the width of div.text. Can this be done with only CSS or does it require JS and dynamically setting background-position?
Use a pseudo element that you make relative to the background element
.background {
background: yellow;
text-align: center;
position: relative;
z-index: 0;
}
.text {
display: inline-block;
color: #fff;
margin: 20px;
padding: 20px;
clip-path: inset(0); /* clip to only text element */
}
.text:before {
content: "";
position: absolute;
z-index: -1;
inset: 0;
background: url(https://picsum.photos/id/1056/800/600) center/cover;
}
/* to illustrate */
.text:hover {
clip-path: none;
}
<div class="background">
<div class="text">
My text is in here
</div>
</div>
Here is one way of doing what you want through JS. The image is in the background element, and it is clipped according to the dimensions of the child element. There's a resize observer applied to the child element to trigger the calculation of the clipping mask whenever the dimensions of the child change.
I've added an animation to show how the clipping is calculated in real-time, but as you can see there is some slight stutter.
let text = document.querySelector('.text');
let bg = document.querySelector('.background');
let observer = new ResizeObserver(() => {
calculateClipPath(bg, text);
})
observer.observe(text);
function calculateClipPath (parent, child) {
parent.style.clipPath = `inset(
${child.offsetTop}px
${parent.clientWidth - (child.offsetLeft + child.clientWidth)}px
${parent.clientHeight - (child.offsetTop + child.clientHeight)}px
${child.offsetLeft}px
)`;
}
.background {
background: url(https://c4.wallpaperflare.com/wallpaper/368/148/1024/flowers-roses-drawing-light-wallpaper-preview.jpg);
text-align: center;
position: relative;
}
.text {
display: inline-block;
margin: 20px;
padding: 40px;
width: 200px;
animation: 3s infinite change;
}
#keyframes change {
0% {
width: 200px;
}
50% {
width: 150px;
}
100% {
width: 200px;
}
}
<div class="background">
<div class="text">
My text is in here
</div>
</div>
I'm still experimenting to see if there is a purely CSS version of the solution because that would always be smoother than the JS solution. If I can figure it out, I'll edit this answer and add it here

sticky of section only once reached in elementor

I have tried many different setups, including snap to the page, and sticky with motion effect and I cannot seem to replicate this well at all. I would love to achieve the section to "stick" only once reached and then the content inside to have motion effect of the scroll and once content is done, remove sticky to resume page. The effect I am looking for is very much this effect on this page https://eiger-extreme.mammut.com/en/technology (the blue section) I have done a short screencast of the effect https://www.screencast.com/t/c8P1xsWOfx9q. I'm happy to even use plugins for elementor if needed
Looks like the left "sticky" section is just a lot of li-elements stacked on top of eachother? E.g. a very high ul-list. Sorry if I'm totally off here, but you can probably create elements that takes up 100vh (or more) in left column, and then make right section sticky? The animation (fade text in and out, changing right column background-image, and change the graphics in right column) could be done with javascript. This is not a good final approach, but it'll maybe give you some sort of help:
// Just a simple scroll-animation that add class to change bg (not implemented to revert bg when scrolling up again)
document.addEventListener('scroll', function(e) {
const heading1 = document.querySelector(".heading1").getBoundingClientRect();
const heading2 = document.querySelector(".heading2").getBoundingClientRect();
const heading3 = document.querySelector(".heading3").getBoundingClientRect();
const heading4 = document.querySelector(".heading4").getBoundingClientRect();
if (heading1.top <= 0 && heading1.top > -100) {
document.querySelector(".s2c2").classList.add("heading1bg");
} else if (heading2.top <= 0 && heading2.top > -100) {
document.querySelector(".s2c2").classList.add("heading2bg");
} else if (heading3.top <= 0 && heading3.top > -100) {
document.querySelector(".s2c2").classList.add("heading3bg");
}
});
body {
margin: 0;
color: white;
}
.s1 {
background-color: red;
padding: 30px;
height: 100vh;
}
.s2 {
display: flex;
flex-direction: row;
}
.s2c1 {
background-color: blue;
flex: 1 0 50%;
padding: 30px;
}
.s2c1 > div {
margin: 50vh auto;
text-align: center;
font-size: 30px;
font-weight: bold;
}
.s2c2 {
background-color: green;
flex: 1 0 50%;
padding: 30px;
}
.s2c2 > div {
position: -webkit-sticky;
position: sticky;
margin: 50% 0;
top: 50%;
text-align: center;
}
.s3 {
background-color: cadetblue;
padding: 30px;
height: 100vh;
}
.heading1bg {
background-color: chocolate;
transition: background-color 200ms linear;
}
.heading2bg {
background-color: purple;
transition: background-color 200ms linear;
}
.heading3bg {
background-color: orange;
transition: background-color 200ms linear;
}
<div class="container">
<div class="s1">First section</div>
<div class="s2">
<div class="s2c1">
<div class="heading1">Heading 1</div>
<div class="heading2">Heading 2</div>
<div class="heading3">Heading 3</div>
<div class="heading4">Heading 4</div>
</div>
<div class="s2c2">
<div>Fixed graphic</div>
</div>
</div>
<div class="s3">Last section</div>
</div>
Run code snippet in full page for best view

Change appearance of sticky element when it reaches sticky position

I have create a bottom div that is present all the time when scrolling the site. Its "natural" stop is right after the footer. When I do scroll, and it's not at the footer, it is a bit transparent. However, what I would like to do is when the sticky div reaches the bottom (i.e. its "true" position), then the background changes or something like that.
Is that possible WITHOUT using JS or something like that ?
Updated with a fiddle:
https://jsfiddle.net/octvg6mn/
HTML:
<div class="largeDiv"></div>
<div class="stickyDiv">Test</div>
CSS:
.largeDiv {
height: 1500px;
width: 100%;
background: #cccccc;
}
.stickyDiv {
position: sticky;
bottom: 0px;
text-align: center;
background: blue;
color: white;
opacity: 0.8;
padding: 25px;
}
.stickyDiv:hover {
opacity: 1.0;
}
So as you can see in the fiddle, the sticky has a light opacity while scrolling, but when I reach the bottom, where it is supposed to be, I would like it to turn the opacity into 1.0 or something like, just like when hovering the mouse.
You can apply an opaque background to the container to simulate this. When the sticky element will reach the bottom that background will hide the transparency:
.largeDiv {
height: 1500px;
width: 100%;
background: #cccccc;
}
.container {
background:rgba(0,0,255,1);
}
.stickyDiv {
position: sticky;
bottom: 0px;
text-align: center;
background:rgba(0,0,255,0.5);
color: white;
padding: 25px;
}
.stickyDiv:hover {
background:rgba(0,0,255,1);
}
<div class="container">
<div class="largeDiv"></div>
<div class="stickyDiv">Test</div>
</div>

What is causing the jump during this collapse or expand CSS transition?

I realize there are CSS/JS animation libraries which would accomplish this, but I am learning CSS Transitions and would like to accomplish this with minimal JS. (I quite enjoy CSS :)
I have several flex-item columns equally sized with flex-grow: 1. I would like a click on a column header to shrink or expand the column, the header itself should remain visible (so that it may be clicked to expand). As display is not an animateable CSS property, I am attempting this with a 2 second transition on width: 0; opacity: 0; on the flex-item content (other than the header) and flex-grow: 0 on the flex-item itself.
I am trying eliminate an undesirable "jump" at the end of a collapse and at the start of an expand.
Despite being the same duration and presumably firing at the same time (after a class change on click), it seems the flex-grow transition is not synchronized with the width/opacity transition of the content, and so the flex-grow transition finishes "too early" (before the content is width 0), then jumps the final bit after the width transition finishes. If I make the flex-grow transition longer (than the width transition) and delay it, the jump is reduced.
I'm trying to understand the exact interaction to eliminate the jump without magic number hackery.
Here is a CodePen: https://codepen.io/richardkmichael/pen/abzYOjB
document.querySelectorAll(".collapsible").forEach(function(c) {
c.addEventListener("click", function(e) {
this.classList.toggle("collapsed");
});
c.addEventListener("transitionrun", function(e) {
this.classList.add("transitioning");
});
c.addEventListener("transitionend", function(e) {
this.classList.remove("transitioning");
// Set `display: none;` on contained div?
// Perhaps unnecessary, since `width: 0`?
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
p {
margin: 1rem;
}
.container {
outline: 1px solid blue;
padding: 1rem;
margin-bottom: 3rem;
display: flex;
}
.collapsible {
outline: 1px solid red;
text-align: center;
/* Lengthen transition and/or increase delay to remove jump. */
/* Permits width/opacity transition to complete? */
flex-grow: 1;
transition: flex-grow 2.5s 0.5s;
}
.collapsible.collapsed {
flex-grow: 0;
}
.collapsible div {
outline: 1px solid green;
opacity: 1;
width: 100%;
transition: opacity 2s, width 2s;
}
.collapsed div {
outline: 1px solid purple;
opacity: 0;
width: 0;
overflow: hidden;
white-space: nowrap;
}
.transitioning div {
/* Debugging. */
background: cyan;
/* Needed during transition to full-size. */
overflow: hidden;
white-space: nowrap;
}
<div class="container">
<div class="collapsible">
<h3>One</h3>
<div>This is item 1.</div>
</div>
<div class="collapsible">
<h3>Two</h3>
<div>This is item 2.</div>
</div>
</div>
<p>
The aim is to smoothly eliminate the column content, leaving only the header.
</p>
<p>
Click a column header ("One" or "Two") to collapse the column; click again to expand.</p>
<p>
What is causing the jump near the end of the collapse or expand transition?
</p>
If it helps convey the objective, my use-case is a calendar in week-view, where the days (Mon, etc.) are the columns.
I guess the issue is due to width:100% and more precisely the use of percentange value creating a complex calculation in order to resolve it.
You may try to animate max-width instead. The only drawback is that you will have a delay at the start when using a big value but you can adjust the transition to rectify this:
document.querySelectorAll(".collapsible").forEach(function(c) {
c.addEventListener("click", function(e) {
this.classList.toggle("collapsed");
});
c.addEventListener("transitionrun", function(e) {
this.classList.add("transitioning");
});
c.addEventListener("transitionend", function(e) {
this.classList.remove("transitioning");
// Set `display: none;` on contained div?
// Perhaps unnecessary, since `width: 0`?
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
p {
margin: 1rem;
}
.container {
outline: 1px solid blue;
padding: 1rem;
margin-bottom: 3rem;
display: flex;
}
.collapsible {
outline: 1px solid red;
text-align: center;
/* Lengthen transition and/or increase delay to remove jump. */
/* Permits width/opacity transition to complete? */
flex-grow: 1;
transition: flex-grow 2.5s 0.5s;
}
.collapsible.collapsed {
flex-grow: 0;
}
.collapsible div {
outline: 1px solid green;
opacity: 1;
max-width: 100vw;
transition: opacity 2s, max-width 2s;
}
.collapsed div {
outline: 1px solid purple;
opacity: 0;
max-width: 0;
overflow: hidden;
white-space: nowrap;
}
.transitioning div {
/* Debugging. */
background: cyan;
/* Needed during transition to full-size. */
overflow: hidden;
white-space: nowrap;
}
<div class="container">
<div class="collapsible">
<h3>One</h3>
<div>This is item 1.</div>
</div>
<div class="collapsible">
<h3>Two</h3>
<div>This is item 2.</div>
</div>
</div>
<p>
The aim is to smoothly eliminate the column content, leaving only the header.
</p>
<p>
Click a column header ("One" or "Two") to collapse the column; click again to expand.</p>
<p>
What is causing the jump near the end of the collapse or expand transition?
</p>
The short answer:
On your .collapsed div (around line 46 in your codepen) change:
width: 0
to
width: 0%

Slide-in from the left with width: auto

I'm implementing a toolbar using flex boxes and want optional sections to appear by smoothly sliding in from the left.
At the start, the bar should look as if the new element didn't exist, and at the end as if the animation never took place
Animating 0% { margin-left: -<width> } works exactly as intended, but only when the element's width is known at that point; I need it to work with width: auto (i.e. unset width) or other non-lengths like when flexing.
#keyframes appear {
0% { margin-left: -30ch; }
}
div.a {
width: 30ch; /* << should work without that */
flex: none; /* << stretch: should work with flex */
animation: appear 1s ease-in-out infinite alternate;
}
.root {
margin: 10px;
border: 1px solid black;
display: flex;
overflow: hidden;
}
.a, .b, .c {
box-sizing: border-box;
padding: 5px;
flex: 1;
}
.b { flex: 2 }
.a { background: #88f }
.b { background: #8d8 }
.c { background: #f88 }
<div class="root">
<div class="a">Some new stuff</div>
<div class="b">Hello</div>
<div class="c">World</div>
</div>
<div class="root">
<div class="a">Some longer content sliding in</div>
<div class="b">Hello</div>
<div class="c">World</div>
</div>
Width:auto; is not animatable. Use max-width:1000px; or something larger than the width will ever need to be. So the code would be:
width:auto;
max-width:1000px;

Resources