I'm trying to create a custom drop down menu in ReactJs. When a user clicks the heading, a list of items appears below it. This works but I want the div that contains both the heading and the list (.db-container) to in increase its height depending on if the list (.db-list) is visible or not. I can use display:none/block or position:absolute/relative with transform:scaleY(0)/scaleY(1) on .db-list but then I cannot animate it or use transitions. I've tried using visibility:hidden/visible which does hide the list but the container remains its full size.
I am using ReactJs and in the code snippet below, the handleClick() function just changes isVisible from true to false and vice versa.
.db-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.db-list {
transform: scaleY(0);
transform-origin: top center;
position: absolute;
}
.visible-true {
transform: scaleY(1) !important;
position: relative !important;
}
<div className='db-container'>
<h3 className='db-name' onClick={handleClick}>Headinf</h3>
<ul className={`db-list visible-${isVisible}`}>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
</ul>
</div>
Is there a way to get .db-container to change its height depending on the visibility of .db-list and use animations or transitions?
Use max-height and overflow:hidden. These are animatable. Needs a bit of tidying up but you'll get the general idea. Setting max-height means the element will just size itself to the content as long as you set it to a high enough value. (I've made it transform on :hover but you can change it to a click in react)
.db-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.db-list {
max-height:0px;
overflow: hidden;
transition: max-height 1s;
}
.db-name:hover + .db-list {
max-height:1000px;
}
Instead of shrinking, you can use position:absolute then delay your component from re-rendering when hiding for a few millisecond then animate.
I got this problem when I built a custom drawer menu. However, when I hide the component it doesn't animate because you can't animate what you can't see. So I had to delay the component re-render then set right to a negative number to move it out of the view port visible part with animation. After that hide the component.
If you use height(to hide by shrinking); it works, but for a second you will see the component children get misplace and it doesn't look great.
Related
I have a notifications container which has fixed position on the right of the screen, but has no size by default. Each time a notification is rendered, it will populate the container, giving it width and height, and the notifications will appear one after the other in a column. I chose to use flex, but the same thing can be achieved without flex, and instead giving each child a margin-bottom for the gap.
.notifications {
position: fixed;
top: 5rem;
right: 1rem;
width: auto;
height: auto;
gap: 0.5rem;
display: flex;
flex-direction: column;
}
The notification itself has a lot of parts to it and the implementation isn't really relevant, so to simplify, Each notification has basic slide-in and slide-out animations:
.favorites-notification {
// other styles
transform: translateX(200%);
opacity: 0;
animation: slideIn 0.375s ease-out forwards, slideOut 0.375s ease-out 2.625s forwards;
}
#keyframes slideIn {
100% {
transform: translateX(0%);
opacity: 1;
}
}
#keyframes slideOut {
100% {
transform: translateY(-100%);
opacity: 0;
}
}
This looks pretty good when only one notification is rendered on the screen. However, when there are multiple notifications, in a top to bottom list, the remaining notifications will snap in place to the empty space left behind by the unmounted notification. Each notification's lifespan is set to 3 seconds with a setTimeout in useEffect on mount. How do I get the remaining elements to slide up to fill the position left by the unmounted notification, rather than just snap in place when the notification unmounts? I want the other notifications to slide up at the same time as the highest one is sliding up and out so it looks smooth.
I've tried collapsing the height of the notification to remove in the slideout animation, but it doesn't achieve the desired result and it's very unsmooth. I also don't want to affect the height of the notification that's sliding out because it lingers on the screen for a bit, and that's not the affect I'm going for. I know that translate doesn't affect document flow, so the DOM still thinks the element is where it originally was until it unmounts.
All help is appreciated. If my question/explanation isn't clear, please point that out and I'll revise it.
Edit: Using the top property didn't seem to work either. Setting negative margin-top is better, but it starts off smooth and still snaps at the end. To make negative margin work smoothly, I have to know the exact size of the notification, and that varies.
I am creating a mega menu trying to keep to current standards; CSS, jquery, bootstrap.
Google Images - Mega Menu
My menu is for product categories, which I want dynamically created in columns newspaper style. Easy enough, I have that working nicely. It requires a fixed height, which makes it overflow horizontally.
My current thinking is to, onload, increase the heights of each sub menu until there is no horizontal overflow. I can get it working with some manual intervention, which defeats to purpose of a dynamic layout.
.container {
width: 800px;
height: 100px;
display: flex;
flex: 1 1 auto;
}
.container .wrap {
display: flex;
flex: 1 1 auto;
flex-flow: column wrap;
align-content: flex-start;
}
.container .wrap .item {
width: 150px;
}
<div class="container">
<div class="wrap">
<div class="item">Item 1<br>a<br>b</div>
<div class="item">Item 2<br>a</div>
<div class="item">Item 3<br>a<br>b<br>c<br>d</div>
<div class="item">Item 4<br>a</div>
<div class="item">Item 5<br>a</div>
</div>
</div>
This style works perfectly aligning items top to bottom, left to right, as required.
Now I am trying to increase the height of each sub menu as required. Best solution is to detect width overflow and increase height to compensate.
I've tried native JS and jQuery, I seem to be getting stuck running it in a function. I can detect overflow and increase height, as my code below. What I want is to have the if statements in a loop and exit when no more overflow, and end up with a perfect fit.
EDIT
Current working solution
$(function () {
$(".container").each( function( index, element ){
var my_height = $(element).outerHeight();
if( $(element).prop('scrollWidth') > $(element).outerWidth() ) {
while( $(element).prop('scrollWidth') > $(element).outerWidth() )
{
my_height += 100;
$(element).css('height', my_height + "px");
}
}
});
});
Works exactly as intended.
EDIT
Updated question. Is there a better solution? Something that doesn't involve looping over every item every page. Pure CSS would be nice, but don't think it's possible just yet.
Solved my transition issue. When trying to update the height in a loop as above, transition CSS interferes and creates an infinite loop. Was an easy fix, I added a new class to .container .transition and added $(element).removeClass("transition"); to the start or the loop and $(element).addClass("transition"); to the end. This removes the transition CSS from the inner loop and adds it back at the end.
FINAL EDIT
My first attempt was using css columns, but I couldn't get it working to my spec. It wasn't until reading the accepted answer below I revisited columns and after some testing got it working.
Ref https://developer.mozilla.org/en-US/docs/Web/CSS/columns
There is a way of doing this using pure CSS using the column CSS properties:
Base CSS:
.container {
width: 800px;
}
.container .wrap {}
.container .wrap .item {
width: 150px;
}
To have columns of a fixed width:
.container .wrap {
column-width: 125px;
}
To have a set number of columns:
.container .wrap {
column-count: 5;
}
JSFiddle
Another answer giving details about browser support
Say you have a dropdown with a lot of options that overflow the page height. I know I can use overflow: auto to make it scroll, but only if I set it a max-height. How do I set a max-height that ensures the element won't overflow the browser window?
Like in this image
The left is what it's like now. Dropdown overflows page. The right is what it should be like -- the dropdown is resized to be height of just under the page height.
I've tried setting max-height to different values like 45vh since the dropdown is about halfway down the page, but this needs to fit all types of screen sizes so isn't flexible enough.
CSS solutions preferred in this case.
You can calculate the current distance between the dropdown and the bottom of the page (https://stackoverflow.com/a/7656176/5370933) and append styles with this value.
.myDropdown {
max-height: myDistance;
overflow: scroll
}
I think something like that could works. But you will have to use some JS to get the distance dynamically (depend on the user screen and/or user scroll before the dropdown opening...)
If I understood correctly the layout of your web page, the dropdown is the last element (well maybe) in the page.
What you could do is, first, add this lines to your main page container:
#page {
min-height: 100vh; /* Or the value you like most */
}
Now we have access to the full height of the document.
Next, you can simply use flexbox's space-between or space-around value to keep the dropdown on the bottom of the page (like footers).
But now, you want a little space between the end of the page and the dropdown. Simply add a margin-bottom and its done.
Now be aware that, I understand that there may be a footer or something below the dropdown. You can implement this solution in any container.
This isn't a bug-free solution, but it doesn't require javascript.
Here is a working example.
function _test_add(){
document.getElementById("dropdown").innerHTML += "<li>Item</li>";
}
#page {
min-height:100vh;
display:flex;
flex-direction: column;
justify-content: space-between;
}
#addbtn {
margin:0 auto;
}
/*
* Fictif Content
*/
#main-content {
height: 50vh;
display:flex;
justify-content:center;
align-items:center;
background-color:gray;
}
#dropdown {
min-height: 8em;
max-height: 18em;
background-color:#f1f1f1;
padding: 0;
list-style-type: none;
overflow: auto;
margin-bottom:4em;
border: solid black 2px;
}
#dropdown li {
padding:1em;
}
#dropdown li:nth-child(odd) {
background-color: #fafafa;
}
<div id="page">
<div id="main-content">
Main Content
</div>
<button id="addbtn" onclick="_test_add()">[TEST] Add items in dropdown</button>
<ul id="dropdown">
<li>Item</li>
<li>Item</li>
<li>Item</li>
</ul>
</div>
I have two elements, and the top one's visibility is controlled by a v-if on a simple boolean.
transition(name="fade")
#element1(v-if="showFirst")
p Foo
#element2
p Bar
The first element is wrapped in a <transition> tag, exactly as per the Vue documentation.
However, while this does create a fading animation, the rest of the content on the page still jumps very jarringly.
How can I create a transition that will also smoothly transform the position of any and all siblings that follow?
A fiddle demoing this issue.
You need to use a transition-group and key your dynamic div and static div
<transition-group name="fade">
<div v-if="switc" key="dynamic" class="animated">
...
</div>
<div key="main-content" class="animated">
...
</div>
</transition-group>
And use this css classes
.fade-enter,
.fade-leave-active {
opacity: 0;
transform: translateY(-10px);
}
.fade-leave-active {
position: absolute;
}
.animated {
transition: all 0.5s;
/*display: flex;*/
width: 100%;
}
The real trick is to change position to absolute when leaving, then any other content can take correct position.
To know more about how Vue animate things please see this FLIP explanation post
And please see this working fiddle
https://jsfiddle.net/bjfhth7c/4/
Edit
By mistake I did set display: flex; in .animated class, that was causing to every inner element to render in a strange way.
So now, I completely remove .animate class, and instead apply transition: all 0.5s and width:100% to every direct inner element of .wrapper
My final scss looks like this:
.wrapper {
display: flex;
flex-direction: column;
>* {
transition: all 0.5s;
width:100%;
};
}
.fade-enter,
.fade-leave-active {
opacity: 0;
transform: translateY(-10px);
}
.fade-leave-active {
position: absolute;
}
Flex layout is a extend subject, but in short for this particular case flex-direction: column is arranging elements one bellows previous one.
If one of those elements has absolute position will be ignored in flex layout so any other elements will be redistributed on available space.
Please see this guide about flexbox and last working fiddle hope it helps.
You can use a slideDown/slideUp animation instead. For achieve this you don't need to know a height of a sliding element, the principles of max-height transition explained there.
So, as a result it will cause animated moving of elements below target.
Check out my example based on your fiddle.
vue js provides different transition classes, you have to use those properly to smooth the transition, I have tried with your example in this fiddle with some CSS, have a look.
.fade-enter-active, .fade-leave-active {
transition: all .5s;
height: 100px;
opacity: 0.5;
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
height: 0px;
opacity: 0;
}
Some details from documentation:
There are six classes applied for enter/leave transitions.
v-enter: Starting state for enter. Added before element is inserted, removed one frame after element is inserted.
v-enter-active: Active state for enter. Applied during the entire entering phase. Added before element is inserted, removed when transition/animation finishes. This class can be used to define the duration, delay and easing curve for the entering transition.
v-enter-to: Only available in versions >=2.1.8. Ending state for enter. Added one frame after element is inserted (at the same time v-enter is removed), removed when transition/animation finishes.
v-leave: Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame.
v-leave-active: Active state for leave. Applied during the entire leaving phase. Added immediately when leave transition is triggered, removed when the transition/animation finishes. This class can be used to define the duration, delay and easing curve for the leaving transition.
v-leave-to: Only available in versions >=2.1.8. Ending state for leave. Added one frame after a leaving transition is triggered (at the same time 7. v-leave is removed), removed when the transition/animation finishes.
You can as well use CSS animations where you can provide on different phases of transition what will be your css property to make your transitions more smooth, like following and demo fiddle:
.fade-enter-active {
animation: bounce-in .5s;
}
.fade-leave-active {
animation: bounce-out .5s;
}
#keyframes bounce-in {
0% {
height: 5px;
}
30% {
height: 30px;
}
50% {
height: 50px;
}
100% {
height: 100px;
}
}
#keyframes bounce-out {
0% {
height: 90px;
}
50% {
height: 50px;
}
100% {
height: 0px;
}
}
I am trying to make list items "zoom" when selected. I'm having trouble finding the best way to make this happen. I'm using GSAP and the javascript is working just fine, but I'm having a hard time finding which property would best achieve the desired "zoom" effect.
https://jsfiddle.net/Leahp374/1/
At the moment, I am attempting to use CSS perspective and translate3d to move the item closer in the Z direction. However, since the perspective-origin is at 50% 50%, and since the list item is centered in its parent container, the first and last list items move towards the top and bottom, respectively. Instead, I'd like them to all behave as the middle list item.
Using something like font-size will cause the element to 're-center' based on it's new width, which is not desirable.
What is the best way to achieve the "zoom" effect?
Is this the desired 'zoom' effect that you are looking for?
You can use scale transform along with transform origin to get the desired zoom effect.
html, body {
height: 100%;
}
div {
perspective: 600px;
width: 100%;
height: 100%;
background: red;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
li:nth-child(1) {
transform: scale(1.5);
transform-origin:-40%;
}
<div>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>