Transition on multiple elements at the same time using JavaScript - javascript

Cheers! I'm having an issue debugging certain behavior that occurs mostly in the Chrome browser. Here is the simplified example: https://jsfiddle.net/pd3xb2uo/
The objective is to transition multiple elements via JS code at the same time. In the example when you click on the button, items are moved to the left using translate3d added via JS. It works fine, but there are some caveats:
There is a small gap appearing between the items most of the time
Sometimes when you click faster on the button there is a large gap appearing between the items.
Here are the screenshots of both cases:
Any help or ideas on why it is happening would be highly appreciated:) It looks like there is a few milliseconds delay before the style attribute is updated on certain elements, but I have no idea why:/

The problem occurs because you are transitioning 100 elements at the same time and because of half-pixel transitions.
If you know how wide and how many elements you have, then you can do it like so:
const container = document.querySelector('.container-inner');
for (let i = 1; i < 100; i++) {
const div = document.createElement('div');
div.classList.add('element');
div.textContent = `Element ${i}`;
container.appendChild(div);
}
let transition = 0;
document.querySelector('button').addEventListener('click', () => {
transition -= 100;
container.style.transform = `translateX(${transition}px)`;
});
.container{
width: 100%;
overflow: hidden;
}
.container-inner{
display: flex;
flex-direction: row;
transition: transform .3s;
}
.element {
width: 100px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
text-align: center;
transition: transform .3s;
background-color: #A67583;
}
<button>Move</button>
<div class="container">
<div class="container-inner"></div>
</div>
Now only one element gets transitioned and it is working smoothly.

Related

CSS transition is ignored due to parents display value?

There are plenty other post regarding CSS transition not working due to the usage of display and I do get the point. The proposed solutions are mostly the usage of opacity and visibility instead of display. Well I dind't want to do it that way even if I could as my positioning is absolute and wouldn't take any space with visibility set to 0, just like with display: none.
However, when I've read this thread, where the accepted answer says "...cannot be applied to a class that is transitioning from display: block; to display: none;" I thought I'm smart and applied the transition to it's child elements, meaning the parent toggles from display: none to display: flex and the children transition from opacity: 0 to opacity: 1. It's still not working and my assumption is that this is due to the same issue of display/transition not wanting each other, but my question is why? First I thought the display value may be inherited but it's not as the children aren't flex.
could anyone explain why it behaves like that?
To as well provide some code here is my react code using styled components for the styling.
const NavigationWrapper = styled.nav`
& {
position: relative;
display: flex;
flex: 1 0;
align-items: center;
justify-content: flex-end;
}
#media (max-device-width: 992px) {
& button {
border: none;
background-color: red;
margin-right: 10px;
}
}
#media (min-device-width: 993px) {
& button {
display: none;
}
}
`;
const NavList = styled.ul`
& {
list-style: none;
display: flex;
margin: 0;
padding: 0;
}
& li {
padding: 10px 20px;
}
#media (max-device-width: 992px) {
& {
position: absolute;
display: ${(props) => (props.isMobileNav ? "flex" : "none")};
flex-direction: column;
right: 0;
top: 100%;
width: 100vw;
background-color: transparent;
}
& li {
opacity: ${(props) => (props.isMobileNav ? 1 : 0)};
background-color: white;
transition: opacity 1s ease;
}
}
`;
const Navigation = (props) => {
const [isMobileNav, setIsMobileNav] = useState(false);
return (
<NavigationWrapper>
<button onClick={() => setIsMobileNav(!isMobileNav)}></button>
<NavList isMobileNav={isMobileNav}>
<li>Blog</li>
<li>Profiles</li>
<li>Contact</li>
</NavList>
</NavigationWrapper>
);
};
EDIT: I tried it obviously the other way around with opacity on parent and display on children, just to try and, yes, it works. But leads to the solution I didn't want as I could set visibility to hidden or opacity to 0. I just want to know why the visibility of the parent affects the transition of the child.
Well, an element with display: none; effectively gets pulled out of the flow. The code is still there, but it is treated as if it is not.
If a parent element is "removed" in this way, all children of that parent are "removed" with it, and this happens instantly. So that is why you can't do transitions of child elements, when the parent is "gone".
A solution though (if you absolutely have to use display), could be to use a setTimeout() that waits for the duration of the transition, before setting display: none; (the other way, setting display: flex; should work with transitions on child elements just fine).
Something like this, although this simple example poses issues regarding unmounting, and you should quard it with a cancel mechanism, that can interrupt the timeout when unmounting the component:
<button
onClick={() => {
// Delayed
if (isMobileNav) {
setTimeout(() => {
setIsMobileNav(false);
}, 1000);
return;
}
// Instant
setIsMobileNav(true);
}}
>
</button>

Dynamically flexible width & number items with flex

I have a set of tags, that I want to show in the client. However, sometimes you might have too many tags and you want to show only one row of tags maximized to your body's width without setting a fixed number of columns or item width, and adding a show more button at the end of the tag list with the same style as a tag.
I have achieved this using Javascript in my Angular project by doing the following:
Find out the width of your tags container dynamically, with ViewChild on my content container:
let contentWidth = this.contentContainer.nativeElement.clientWidth;
Calculate the text width of the see more button and use it to calculate the new content width minus see more button width:
Calculating text function does the following:
const canvas = document.createElement('canvas'); // create a canvas
const context = canvas.getContext('2d'); // get the context
context.font = '12px avertastd-bold'; // set up your font and size
And calculate the text width:
const seeMoreButtonWidth = context.measureText(seeMoreButtonText).width;
Create a new array variable 'previewTags' which will hold the tags that are visible when the tags body is in collapsed state, and fill in as many tags as you can by calculating each tag's width with it's content text you receive from the API by checking if the next tag + its padding (static value) fits into the width.
(Not runnable here)
for (const tag of this.data.tags) {
const width = context.measureText(tag).width;
if (contentWidth - (width + this.tagsPadding) > 0) {
previewTags.push({text: tag});
contentWidth -= (width + this.tagsPadding);
} else {
break;
}
}
Push the see more button at the end of previewTags list:
previewTags.push({text: seeMoreButtonText, isButton: true});
And it looks like this in the html:
<ng-container *ngFor="let tag of previewTags">
<div class="tag" [ngClass]="{'see-more-button': tag.isButton}">{{tag.text}}</div>
</ng-container>
Output:
Resize:
As you see, now the tags are flexiable (this code does not include the show more functionality).
After giving you this background and understanding of what I am doing, I would love to ask if this is possible to achieve with css or less JavaScript intervation?
Something like this could be a pure css solution if your tags have a constant height. I just let the flex-list wrap around and then don't show the overlap.
.content_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: rows;
}
.tag_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: rows;
flex-wrap: wrap;
width: 80%;
height: 32px;
overflow: hidden;
}
.tag_wrapper div {
width:100px;
height:30px;
border: 1px solid black;
}
button {
flex-grow: 4;
}
<div class="content_wrapper">
<div class=tag_wrapper>
<div>Tag1</div>
<div>Tag2</div>
<div>Tag3</div>
<div>Tag4</div>
<div>Tag5</div>
<div>Tag6</div>
<div>Tag7</div>
<div>Tag8</div>
<div>Tag9</div>
</div>
<button>See more</button>
You could probably make the "See more" button solution more elegant, to not have as much white space but I'll leave that to you :)
Here is some javascript to remove the see-more button if it's not needed.
(OBS) this only works if all the tags are the exact same width and have the same margin. I did this to avoid looping through all values and checking their width individually.
(I know the list is in the wrong order, I made it like that to get the see-more button fit in well without having to tinker a bunch.
function getWidthWithMargin(elem) {
var style = elem.currentStyle || window.getComputedStyle(elem)
margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight)
return(elem.getBoundingClientRect().width + margin)
}
function handleWindowSizeChange() {
let tags = document.getElementsByClassName("tag");
if(tags.length != 0)
{
let tag_width = getWidthWithMargin(tags[0]);
if(tags[0].parentElement.getBoundingClientRect().width/tag_width > tags.length) {
document.getElementById("see-more-button").style.display = "none";
}
else{
document.getElementById("see-more-button").style.display = "block";
}
}
}
window.onload = handleWindowSizeChange;
window.onresize = handleWindowSizeChange;
.content_wrapper {
}
.tag_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: row-reverse;
flex-wrap: wrap;
width: 100%;
height: 32px;
overflow: hidden;
}
.tag_wrapper div {
min-width:100px;
height:30px;
border: 1px solid black;
margin: 10px;
}
.tag_wrapper button {
height:30px;
flex-grow: 50;
}
<div class="content_wrapper">
<div class=tag_wrapper>
<button id="see-more-button">See more</button>
<div class="tag">Tag1</div>
<div class="tag">Tag2</div>
<div class="tag">Tag3</div>
<div class="tag">Tag4</div>
<div class="tag">Tag5</div>
<div class="tag">Tag6</div>
<div class="tag">Tag7</div>
<div class="tag">Tag8</div>
</div>

from :hover to a togglebutton in react

What I am trying to do:
I have a card that when hovered over expands and does a bunch of different animations i worked on. this is great for desktop and not mobile. So when the screen is a certain res I make a toggle button visible. I want this button in react to when clicked on enable the save:hover State its grabbing from my css.
The Problem
The button is inside the card aleady and everything in the css is setup for the parent div card.
my code structure simplified
<div className="card">
<-- CODE HERE -->
<Button className="myToggleButton" />
<div/>
I cant from what i can tell in css exclusively say myToggleButton:focus do these changes to the following classes since they are parent ones. So i think my only other way to do that is by telling it in react somehow to say that my div is in :hover state, but I can't quite figure out how despite my efforts. Thank you in advance for any help on this.
Instead of activating your animation with the :hover pseudo class, you can simply add or toggle another custom class, which would contain the values to transition or keyframes to animate, to the card element when the button is clicked. In this example I just use a transition, but you could also employ a more complex keyframe animation.
const card = document.querySelector(".card");
const button = document.querySelector(".card > button");
const animate = () => {
card.classList.toggle("small");
card.classList.toggle("big");
}
button.addEventListener("click", animate);
.card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
transition: all 250ms ease-in-out;
}
.small {
width: 200px;
height: 50px;
background-color: gray;
}
.big {
width: 400px;
height: 150px;
background-color: blue;
}
<div class="card small">
My Amazing Card
<button>Animate</button>
</div>

Strange transition behavior for inline elements styles in certain places

This is a jsfiddle example file that replicates the problem: https://jsfiddle.net/Lhr0d6cw/11/
I wanted the element (when clicked) to expand for 6seconds from its original position but notice that when you click the red card (or any card), it doesn't start expanding from the originals position it used to be, but rather from the middle, I assume that its because transition of 6s to top and left is not being applied for some reason.
Only places I was able to make it work properly so far are stackoverflow editor below or by inserting a debugger in the code and doing it manually but when using my localhost or jsfiddle it doesn't transition properly.
This is the same example on stackoverflow which works as desired:
const productCards = document.querySelectorAll(".products__card");
productCards.forEach(c => {
// console.log("clicked1");
c.addEventListener("click", openCard)
});
function openCard(e) {
console.log("clicked");
console.dir(this);
let top = this.getBoundingClientRect().top;
let left = this.getBoundingClientRect().left;
// this.style.transition = "top 0.9s, left 0.9s";
this.style.top = top + "px";
this.style.left = left + "px";
this.style.position = "fixed";
console.log(`top: ${top}, left: ${left}`);
// debugger;
this.classList.add("open");
}
.products {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: center;
min-width: 1000px;
max-width: 1500px;
margin-bottom: 300px;
}
.products .products__card {
display: flex;
flex-direction: column;
width: 150px;
height: 250px;
margin-bottom: 30px;
margin-right: 30px;
margin-left: 30px;
background-color: red;
transform: scale(1);
/* box-shadow: 3px 7px 55px -10px c(very-light); */
transition: width 0.9s, height 0.9s, z-index 0.9s, top 6s, left 6s;
}
.products .products__card.card-1 {
background-color: red;
}
.products .products__card.card-2 {
background-color: blue;
}
.products .products__card.card-3 {
background-color: green;
}
.products .products__card.card-4 {
background-color: yellow;
}
.products .products__card.card-5 {
background-color: pink;
}
.products .products__card.card-6 {
background-color: gray;
}
.products .products__card.open {
width: 550px;
height: 800px;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 120;
box-shadow: 0 0 1000px 1000px c(box-overlay);
}
<div class="products">
<div class="products__card card-1">
</div>
<div class="products__card card-2">
</div>
<div class="products__card card-3">
</div>
<div class="products__card card-4">
</div>
<div class="products__card card-5">
</div>
<div class="products__card card-6">
</div>
</div>
works when debugging:
The strange thing as mentioned above is that my problem in the browser using localhost is also solved when I insert debugger in the code and manually skip through the last step of adding .open class. If you have the same problem in jsfiddle or your own editor, try adding debugger; before this.classList.add("open"); and then open the console and then click the card and go over the last step manually in the console. you will notice that the card expanded from its original place as desired taking 6s to finish which means the transition was applied in this case.
My questions:
Why is transition for top and left only working in certain environments? is it a browser problem? I'm using the latest chrome. does someone know of a better way to achieve the same results?
code comments:
-obviously, 6 seconds is not what I will be using in my code, its used here just to make the transition obvious.
-In my source code, you can see that because I can't transition from position static to position fixed I had to use Javascript to add position fixed style inline to the element before the .open class is added, that way transition can take place properly when .open is added.
-I also added top and left values inline to keep the card in its original place when position: fixed style is applied because as you might know fixed position takes the element out of its flow, so top and left keep it in place.
-I added !important in css .open class because without it I can't override inline css as you might also know.
Thank you
I was able to solve my problem just now by applying a little hack. It seems that in some environments (localhost, jsfiddle) the javascript engine is adding the .open class faster than expected and the fact that it is working fine when debugging (slow process) indicated that to me. so I added a setTimeout() to the last piece of code delayed it by 20. this solved my problem and now it works fine on JSfiddle and on my computer. here is the new edited sample that works:
https://jsfiddle.net/Lhr0d6cw/14/
setTimeout(() => {
this.classList.add("open");
}, 20);
I would still like to know if there is a better way of doing this animation if someone would like to share!

Navigation transition

I am new to jQuery and am teaching myself as I go but am struggling to figure out how to indicate that on up scroll the white navigation background moves up to show the white navigation text on panel 1?
bartaile.com is what I am using as inspiration & the changes I'm making to bartaile's navigation are---> after the user scrolls past the first panel the navigation hides, only when the user scrolls up does the navigation show again, when panel 1 comes back down the white navigation backgrouns slide up to hide and shows white text.
Any help or tips to learn how to do this would be greatly appreciated! :-)
var lastScrollTop = 0;
$(window).on('scroll', function() {
var header = $('.header');
var stage0 = $('.stage-0');
var scrollTop = $(window).scrollTop();
if (scrollTop > lastScrollTop) {
// down scroll
if (scrollTop > stage0.offset().top + stage0.height()) {
header.addClass('hide');
}
} else {
// up scroll
if (scrollTop <= stage0.offset().top + stage0.height()) {
header.removeClass('headerBGchange headerLIchange');
} else {
header.removeClass('hide').addClass('headerBGchange headerLIchange BGupTranistion');
}
}
lastScrollTop = scrollTop;
});
.header {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 80px;
-webkit-transition: top .5s ease;
transition: top .5s ease;
position: fixed;
width: 100%;
top: 0;
background-color: transparent;
overflow: hidden;
}
.header ul {
margin: 20px;
padding: 0;
}
.header ul li {
display: inline-block;
margin-right: 20px;
color: white;
}
.header ul li:last-child {
margin-right: 0;
}
.hide {
top: -80px;
}
.headerBGchange {
Background: white;
}
.BGupTranistion {
}
.header.headerLIchange ul li {
color: Blue;
}
.header.headerLIchange {
border-bottom: 1px solid black;
}'
</style>
<!--stage style--><style>
.stage {
color: #fff;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 100vh;
background-color: white;
border-bottom: 1px solid black;
font-size: 48px;
height: 200px;
width: 100%;
}
.stage-0 {
background: grey;
}
.stage-24 {
background: #433937;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div class="header">
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</div>
<div class="stage stage-0">1</div>
<div class="stage stage-2">3</div>
<div class="stage stage-4">5</div>
<div class="stage stage-6">7</div>
<div class="stage stage-8">9</div>
<div class="stage stage-10">11</div>
<div class="stage stage-12">13</div>
<div class="stage stage-14">15</div>
<div class="stage stage-16">17</div>
<div class="stage stage-18">19</div>
<div class="stage stage-20">21</div>
<div class="stage stage-22">23</div>
You will need to add another container to achieve the effect you're looking for. What you essentially want to have is a container at the top and another container which will fade in and out depending on your scroll behaviour. So how do you achieve that? Create a -Element on top of the page, like your gray box is there at the moment. When scrolling down, do not transform it, instead, fade in another previously hidden container to act as your navigation when not at the top of the page. Now if you scroll back up, check the scroll location, and if the two locations of both containers overlap, start fading out the container you use when not at the top of the page. I do not think there is another solution. I might try and write a codepen on it now, I will edit my post if I had success. You could also try working it out with another div inside the actual header and z-index, though that might turn out really bad.
I have done my best to achieve what you want. Here is the CodePen.
I used two different divs, one called .dynamic-header and one normal header, and I've added a function to detect jQuery In-Viewport.
$.fn.isOnScreen = function(){
var element = this.get(0);
var bounds = element.getBoundingClientRect();
return bounds.top < window.innerHeight && bounds.bottom > 0;
}
I hope this fits your needs. Also, I changed some CSS around, using the Top-Property for the transition. You can outsource all of that into CSS classes and use them instead, but I thought this was the simplest solution for demonstration purposes. Is this what you want?
Edit 1: You named bartaile.com as an example. I took a look at the effect they create and recreated it. What you have to do is basically create a structure like this:
<div class="header-bg"></div>
<div class="header-content">
<ul>
<li>YOUR HEADER</li>
</ul>
</div>
I made another CodePen for this.
The header-bg has a height of 0. The header-content has a height of, lets say, 80px, and a background-color of transparent. Now do NOT check which direction is scrolled. The only important aspect for the effect is, how far are you from the top / is a specific element in viewport? I went for 400px from top. Now when that requirement is met, just fade in the header-bg. It will be inbetween the wrapper and the content, and will provide a background. Together with that, you may also change the color of the header-content, but I did not do that. It is what bartaile.com does, tho, so you might want to include it. Enjoy!
Edit 2: I've edited the CodePen according to your comment. See it in action here. This does the following: A header is there. When scrolling down, it'll disappear. On scroll up, it'll bring up a background, but when scrolling so that scrollTop < 400, the background will fade out. As of what I understood, this is what you want. It uses the structure I posted above.
I check out "bartaile.com" and I have to point out that what they use is a third party lib called 'fullpage'.If you wanna achieve that kind of effect, you should check out this lib fullpage.js. This is a simple and easy to use plugin to create fullscreen scrolling websites (also known as single page websites or onepage sites). It allows the creation of fullscreen scrolling websites, as well as adding some landscape sliders inside the sections of the site.
This plugin can handle "full screen scrolling" and also normal scrolling. You can achieve your effect with this much more easier

Categories

Resources