Using CSS transition on the height property does not produce the desired effect

I'm currently working on a menu interaction that activates on click. Here is the code snippet for reference:

let btn = document.querySelector('.trigger');
let icons = document.querySelector('.icons');
let labels = document.querySelector('.labels');
btn.onclick = function() {
  icons.classList.toggle('active');
  labels.classList.toggle('active');
}
body {
  background: #222;
}

.trigger {
  cursor: pointer;
}

nav {
  position: absolute;
  top: 30px;
  right: 30px;
  color: #222;
}

nav ul.icons {
  background: #fff;
  width: 60px;
  height: 60px;
  overflow: hidden;
  border-radius: 60px;
  transition: 1s ease;
}

nav ul.icons:hover {
  height: 100%;
}

nav ul li {
  list-style: none;
}
<html lang="en">

<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
</head>

<body>
  <nav>
    <ul class="icons">
      <li class="trigger">
        <i class="fas fa-bars"></i>
      </li>
      <li><i class="fas fa-home"></i></li>
      <li><i class="fas fa-users"></i></li>
      <li><i class="fas fa-concierge-bell"></i></li>
      <li><i class="fas fa-pen"></i></li>
      <li><i class="fas fa-phone-alt"></i></li>
    </ul>
  </nav>
</body>

</html>

The challenge I'm facing is that the transition property is not working smoothly with the height in CSS. Have I missed something, and is there a quick solution for this?

Answer №1

It seems the issue arises when trying to transition the height to 100%, which CSS cannot handle immediately.

A quick but imperfect fix:
You could set a fixed height in the :hover state, like 100px. However, this method would require manual adjustments if new items are added or the item sizes change.

My preferred approach:
I would begin by restructuring the HTML slightly and moving the trigger outside of the item list:

<nav>
    <button class="trigger">
        <i class="fas fa-bars"></i>
    </button>
    <ul class="icons fas-container">
        <li><i class="fas fa-home"></i></li>
        <li><i class="fas fa-users"></i></li>
        <li><i class="fas fa-concierge-bell"></i></li>
        <li><i class="fas fa-pen"></i></li>
        <li><i class="fas fa-phone-alt"></i></li>
    </ul>
</nav>

Next, I would adjust the CSS for the .icons class:
(remove the height, add a max-height, and modify the transition)

nav ul.icons {
    background: #fff;
    width: 60px;
    overflow: hidden;
    border-radius: 60px;

    max-height: 0;
    transition: max-height 0.2s ease-out;
}

Lastly, I would utilize JavaScript to manage the max-height property accurately.
For hover effects:

let btn = document.querySelector('.trigger');
let icons = document.querySelector('.icons');
btn.onmouseover = function () {
    icons.style.maxHeight = icons.scrollHeight + "px";
}
btn.onmouseout = function () {
    icons.style.maxHeight = null;
}

Alternatively, for click events:

let btn = document.querySelector('.trigger');
let icons = document.querySelector('.icons');

btn.onclick = function() {
    if (icons.style.maxHeight){
        icons.style.maxHeight = null;
    } else {
        icons.style.maxHeight = icons.scrollHeight + "px";
    }
}

Here are some potentially useful resources:

Snippet:

let btn = document.querySelector('.trigger');
    let icons = document.querySelector('.icons');
    btn.onmouseover = function () {
        icons.style.maxHeight = icons.scrollHeight + "px";
    }
    btn.onmouseout = function () {
        icons.style.maxHeight = null;
    }
body {
        background: #222;
    }

    .trigger {
        cursor: pointer;
    }

    nav {
        position: absolute;
        top: 30px;
        right: 30px;
        color: #222;
    }

    nav ul.icons {
        background: #fff;
        width: 60px;
        overflow: hidden;
        border-radius: 60px;

        max-height: 0;
        transition: max-height 0.2s ease-out;
    }

    nav ul.icons:hover {
        height: 100%;
    }

    nav ul li {
        list-style: none;
    }
<html lang="en">

<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
</head>

<body>
<nav>
    <button class="trigger">
        <i class="fas fa-bars"></i>
    </button>
    <ul class="icons fas-container">
        <li><i class="fas fa-home"></i></li>
        <li><i class="fas fa-users"></i></li>
        <li><i class="fas fa-concierge-bell"></i></li>
        <li><i class="fas fa-pen"></i></li>
        <li><i class="fas fa-phone-alt"></i></li>
    </ul>
</nav>
</body>

</html>

Answer №2

When utilizing CSS transitions with the "height" property, it's important to note that both the starting and ending values must be specified.

To work around this limitation, consider using "max-height" instead of "height". This allows you to exceed the exact height that the element should have at the end of the transition.

let btn = document.querySelector('.trigger');
let icons = document.querySelector('.icons');
let labels = document.querySelector('.labels');
btn.onclick = function() {
  icons.classList.toggle('active');
  labels.classList.toggle('active');
}
body {
  background: #222;
}

.trigger {
  cursor: pointer;
}

nav {
  position: absolute;
  top: 30px;
  right: 30px;
  color: #222;
}

nav ul.icons {
  background: #fff;
  width: 60px;
  max-height: 60px;
  overflow: hidden;
  border-radius: 60px;
  transition: max-height 1s ease;
}

nav ul.icons:hover {
  max-height: 120px;
}

nav ul li {
  list-style: none;
}
<html lang="en">

<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
</head>

<body>
  <nav>
    <ul class="icons">
      <li class="trigger">
        <i class="fas fa-bars"></i>
      </li>
      <li><i class="fas fa-home"></i></li>
      <li><i class="fas fa-users"></i></li>
      <li><i class="fas fa-concierge-bell"></i></li>
      <li><i class="fas fa-pen"></i></li>
      <li><i class="fas fa-phone-alt"></i></li>
    </ul>
  </nav>
</body>

</html>

One downside to be aware of is the risk of a delay in the return transition if the final value set is significantly higher than what is required.

Answer №3

Here is a simple snippet that captures the essence of what you initially intended to create.

[NOTE] : The transition may seem a bit jerky (not as smooth as desired), but this is the current state of it.

document.querySelector('.menu').addEventListener('click', (e) => {
  document.querySelector('ul.icons').classList.toggle('active');
});
* {
  margin: 0;
  padding: 0;
  outline: 0;
  box-sizing: border-box;
}

.navbar {
  position: relative;
  width: 100%;
  height: 100px;
  display: flex;
  padding: 0 4rem;
  align-items: center;
  justify-content: flex-end;
  background-color: #f7f8f9;
  border-bottom: 1px solid #c1c1c1;
}

.navbar .menu i {
  cursor: pointer;
  font-size: 2rem;
}

.navbar .icons {
  opacity: 0;
  list-style: none;
  position: absolute;
  top: calc(100% + 2px);
  right: 3rem;
  display: flex;
  flex-direction: column;
  transition: opacity 250ms ease;
}

.navbar .icons .icon {
  display: grid;
  padding: 1rem;
  flex-shrink: 0;
  min-width: 30px;
  min-height: 30px;
  border-radius: 50%;
  place-items: center;
  background-color: #f7f8f9;
  border: 1px solid #c1c1c1;
  transition: margin-top 250ms ease;
}

.navbar .icons .icon:not(:first-of-type) {
  margin-top: 1rem;
}

.navbar .icons .icon:nth-child(2) {
  margin-top: -62px;
}

.navbar .icons .icon:nth-child(3) {
  margin-top: -62px;
}

.navbar .icons .icon:nth-child(4) {
  margin-top: -62px;
}

.navbar .icons .icon:nth-child(5) {
  margin-top: -62px;
}

.navbar .icons.active {
  opacity: 1;
}

.navbar .icons.active .icon {
  margin-top: 1rem;
}

.navbar .icons.active .icon:nth-child(1) {
  margin-top: 0;
  transition-delay: 250ms;
}

.navbar .icons.active .icon:nth-child(2) {
  transition-delay: 250ms;
}

.navbar .icons.active .icon:nth-child(2) {
  transition-delay: 500ms;
}

.navbar .icons.active .icon:nth-child(3) {
  transition-delay: 750ms;
}

.navbar .icons.active .icon:nth-child(4) {
  transition-delay: 1s;
}

.navbar .icons.active .icon:nth-child(5) {
  transition-delay: 1.25s;
}

.bx {
  font-size: 1.75rem;
}
<link href='https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5d3f3225343e32332e1d6f736d7364">[email protected]</a>/css/boxicons.min.css' rel='stylesheet'>

<nav class="navbar">
  <div class="menu" role="button">
    <i class='bx bx-menu'></i>
  </div>
  <ul class="icons">
    <li class="icon">
      <i class='bx bx-home-alt'></i>
    </li>
    <li class="icon">
      <i class='bx bx-user'></i>
    </li>
    <li class="icon">
      <i class='bx bx-bell'></i>
    </li>
    <li class="icon">
      <i class='bx bx-pen'></i>
    </li>
    <li class="icon">
      <i class='bx bx-phone'></i>
    </li>
  </ul>
</nav>

If you have suggestions to enhance the smoothness of this transition, feel free to edit this answer.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring Material UI: Customizing the styling of components within TablePagination

Is it possible to customize the styling of buttons within the actions panel of the TablePagination component? import { withStyles } from '@material-ui/core'; import MuiTablePagination from '@material-ui/core/TablePagination'; const st ...

Display icon within ng-bind-html when the value is not true

Is there a way to integrate a fontawesome icon into ng-bind-html on ui-select based on a boolean value? <span ng-bind-html="project.IsActive == false && <i class="fa fa-ban" aria-hidden="true"></i> | highlight: $select.search">& ...

I am struggling to find the correct way to fetch images dynamically from Cloudinary and display them as backgrounds. How should I go about implementing this feature successfully

I have been trying to figure out why I am unable to retrieve the image from cloudinary. My goal is to use the image as a background when posting, but it seems like I am not fetching the image correctly. function Post({post}){ return( <div ...

How can I modify my code to ensure that trs and th elements are not selected if their display property is set to none?

I am currently working on creating a filter for my pivot table, but I am unsure of how to dynamically calculate the sum of each row/column based on whether they are displayed or not. If you need any other part of my code, feel free to ask. To hide employee ...

Using the context_processors variable within a Django "if" condition

Developing themes for my Django website is my current project, and one of my goals is to adjust the HTML text color based on a context_processors variable. The context_processors variable options are : Datasystems or Cameroun (I currently have two themes, ...

Trigger file download with ajax and php

I have been using an image picker plugin to select an image and then force it to download. Initially, I hard coded the PHP which worked perfectly fine. The download pop-up appeared and I was able to view the file without any issues. However, when trying to ...

Closing the nested accordion will collapse all sections within the accordion

I'm currently facing an issue with my nested accordions. I've been attempting to nest them in a way that doesn't require writing additional jQuery code for each new one added. As an example, I've created a jsfiddle... https://jsfiddle. ...

I am struggling to render the pages and components in React while using BrowserRouter

Below is the code snippet for App.js and Home.js that I'm working on. My aim is to showcase the Home.js component in the browser. App.js import "./App.css"; import { Route, BrowserRouter } from "react-router-dom"; import Home from ...

Utilizing the zIndex property on a map label does not produce any impact when combined with a GeoJSON layer

Utilizing the Google map label tool, I am trying to showcase certain property from GeoJSON data on a GeoJSON layer. However, the issue arises as the layer has a dark color and the label is appearing blurry behind the GeoJSON data layer. Despite attempting ...

The ultimate guide to personalizing group titles in Angular UI-Select

Is there a way in Angular ui-select to customize the group label? I want to make it larger than the selection items as shown in the image below. https://i.stack.imgur.com/ofcak.png The list is currently grouped by country, but how can I adjust the size o ...

Having trouble with Vue component not updating Vuex state before it loads?

At times, the token is committed to Vuex store while other times it is not. userLogin() { axios.post('api/login', this.logindata,) .then(response => { let token = JSON.parse(localStorage.getItem('token')); t ...

Lazy loading a React grid gallery as you scroll

Currently, I am utilizing React grid gallery to showcase images from this repository: https://github.com/benhowell/react-grid-gallery. However, I am encountering an issue with lazy loading when the user scrolls on the page. <Gallery images={this ...

Issue with OnChange Not Triggering

Why isn't the onchange property firing when typing in my textbox? Check out the code below: VB.NET Dim textBoxUrlYoutube As New TextBox divUrlTextBoxContainer.Controls.Add(textBoxUrlYoutube) textBoxUrlYoutube.CssClass = "textboxyo ...

The selection button's threshold

Welcome to my website design project! I have implemented image buttons for my web page and want to restrict the number of images a user can select. If a user tries to navigate away after selecting more than 3 images, I wish to display an alert message. Her ...

Ways to stop VoiceOver from selecting the background content when a modal is open

Is there a way to prevent VoiceOver from reading the content behind a modal? I tried using aria-modal=true, but it seems VoiceOver does not support this feature by default like NVDA and JAWS do. I found more information about this on . According to the in ...

Pair objects that possess a specific attribute

I have a HTML snippet that I want to modify by adding the CSS class HideEdit to hide specific columns. <th class="rgHeader" style="text-align: left;" scope="col" _events="[object Object]" control="[object Object]" UniqueName="Edit"> This particular ...

Utilizing AngularJS directives with the power of string formatting

Looking at this JSON structure {textTemplate:"Name:{0},Phone:{1}",controls:[{id:1,Name:"Name",type:"text"},{id:2,Name:"Phone",type:"text"}]} I'm unsure how to utilize the directive for converting strings into HTML controls This is what I&apos ...

Angular directive ceases to trigger

I am currently working on implementing an infinite scrolling directive. Initially, when the page loads and I start scrolling, I can see the console log. However, after the first scroll, it stops working. It seems like it only triggers once. Can anyone poi ...

Apply a specific class using JavaScript/jQuery when a user selects a specific timezone from the user interface

Currently, I am coding in HTML with the code below extracted from this website link. The listings under timezone ET are all correct as they align with the accurate dates; however, for other timezones (PT, MT, CT, AT, NT) some shows seem to be on incorrect ...

Dependency management in ReactJS components

I am grappling with the optimal structure for a React component that is composed of other components. Let's look at the first example: <ColorSelect id="color" label={this.state.selectLabel} trigger={{ color: "lime", text: "Lime"}} onPropagateCli ...