How to add slide-down and slide-up animations to dropdown menu

750 Views Asked by At

I have a International country code selection option in my site and for that I want to implement a smooth slide-down and slide-up animation toggle function to my website. In in my case both the side up and down is not working. if the slide down is working than the up is not working and if the up is working than the down is not working. that's why I need experts help to implement this animation effect.

I also found various tutorials in codepen but here also both the animation is not present. I need both slide down and up animation for my dropdown.

const countryData = [
   { name: "Afganistan", code: "93", flag: "afg" },
   { name: "Albania", code: "355", flag: "alb" },
   { name: "Algeria", code: "213", flag: "dza" },
   { name: "American Samoa", code: "1-684", flag: "asm" },
   { name: "Andorra", code: "376", flag: "and" },
   { name: "Angola", code: "244", flag: "ago" },
   { name: "Anguilla", code: "1-264", flag: "aia" },
   { name: "Antarctica", code: "672", flag: "ata" },
   { name: "Antigua and Barbuda", code: "1-268", flag: "atg" },
   { name: "Argentina", code: "54", flag: "arg" },
   { name: "Armenia", code: "374", flag: "arm" },
   { name: "Aruba", code: "297", flag: "abw" },
   { name: "Australia", code: "61", flag: "aus" },
   { name: "Austria", code: "43", flag: "aut" },
   { name: "Azerbaijan", code: "994", flag: "aze" },
   { name: "Bahamas", code: "1-242", flag: "bhs" },
   { name: "Bahrain", code: "973", flag: "bhr" },
   { name: "Bangladesh", code: "880", flag: "bgd" },
   { name: "Barbados", code: "1-246", flag: "brb" },
   { name: "Belarus", code: "375", flag: "blr" },
   { name: "Belgium", code: "32", flag: "bel" },
   { name: "Belize", code: "501", flag: "blz" },
   { name: "Benin", code: "229", flag: "ben" },
   { name: "Bermuda", code: "1-441", flag: "bmu" },
   { name: "Bhutan", code: "975", flag: "btn" },
   { name: "Bolivia", code: "591", flag: "bol" },
   { name: "Bosnia and Herzegovina", code: "387", flag: "bih" },
   { name: "Botswana", code: "267", flag: "bwa" },
   { name: "Brazil", code: "55", flag: "bra" },
   { name: "British Indian Ocean Territory", code: "246", flag: "iot" },
   { name: "British Virgin Islands", code: "1-284", flag: "vgb" },
   { name: "Brunei", code: "673", flag: "brn" },
   { name: "Bulgaria", code: "359", flag: "bgr" },
   { name: "Burkina Faso", code: "226", flag: "bfa" },
   { name: "Burundi", code: "257", flag: "bdi" },
   { name: "Cambodia", code: "855", flag: "khm" },
   { name: "Cameroon", code: "237", flag: "cmr" },
   { name: "Canada", code: "1", flag: "can" },
   { name: "Cape Verde", code: "238", flag: "cpv" },
   { name: "Cayman Islands", code: "1-345", flag: "cym" },
   { name: "Central African Republic", code: "236", flag: "caf" },
   { name: "Chad", code: "235", flag: "tcd" },
   { name: "Chile", code: "56", flag: "chl" },
   { name: "China", code: "86", flag: "chn" },
   { name: "Christmas Island", code: "61", flag: "cxr" },
   { name: "Cocos Islands", code: "61", flag: "cck" },
   { name: "Colombia", code: "57", flag: "col" },
   { name: "Comoros", code: "269", flag: "com" },
   { name: "Cook Islands", code: "682", flag: "cok" },
   { name: "Costa Rica", code: "506", flag: "cri" },
   { name: "Croatia", code: "385", flag: "hrv" },
   { name: "Cuba", code: "53", flag: "cub" },
   { name: "Curacao", code: "599", flag: "cuw" },
   { name: "Cyprus", code: "357", flag: "cyp" },
   { name: "Czech Republic", code: "420", flag: "cze" },
   { name: "Democratic Republic of the Congo", code: "243", flag: "cod" },
   { name: "Denmark", code: "45", flag: "dnk" },
   { name: "Djibouti", code: "253", flag: "dji" },
   { name: "Dominica", code: "1-767", flag: "dma" },
   { name: "Dominican Republic", code: "1-809", flag: "dom" },
   /*------- MORE --------*/
  ];

function registerForm(formRoot) {
  const ccButton = formRoot.querySelector(".cc-telcode");
  const ccContainer = formRoot.querySelector(".cc-container");
  const ccSearchInput = formRoot.querySelector(".cc-search-box");
  const ccList = formRoot.querySelector(".cc-data-list");
  let selectedCountry = "";
  
  
  ccButton.addEventListener("click", handleButton);
  ccSearchInput.addEventListener("input", handleInput);
  ccList.addEventListener("click", handleListClick);
  document.addEventListener("click", handleDocumentClick);
   
     function handleDocumentClick(e) {
    const { parentElement } = e.target;
    document.addEventListener("click", (event) => {
      if (!formRoot.contains(event.target)) {
        ccContainer.classList.remove("show-cc-list");
      }
    });
  }

  function handleButton() {
    ccContainer.classList.toggle("show-cc-list");
    ccList.innerHTML = createListHtml(countryData);
  }
   
     function createListHtml(countryData) {
    return countryData.map((obj) => {
        const { name, code, flag } = obj;
        let isSelected = "";
        if (obj.name == selectedCountry) isSelected = "selected-country";
        return `
          <li class="cc-list-items ${isSelected}" data-name="${name}" data-code="${code}" data-flag="${flag}">
              <div class="flag-icon flag-icon-${flag}"></div>
              <div class="name">${name} (+${code})</div>
          </li>
        `;
      }).join("");
  }
  
    function handleInput(e) {
    const { value } = e.target;
    if (value) {
      const filtered = filterData(countryData, value);
      if (filtered.length) {
        ccList.innerHTML = createListHtml(filtered);
      } else {
        ccList.innerHTML = createNoDataHtml();
      }
    } else {
      ccList.innerHTML = createListHtml(countryData);
    }
  }
  
    function handleListClick(e) {
    const item = e.target.closest("li") || e.target;
    if (item.classList.contains("cc-list-items")) {
      const { code, flag } = item.dataset;
      selectedCountry = item.dataset.name;
      ccButton.innerHTML = createButtonHtml(code, flag);
      ccContainer.classList.remove("show-cc-list");
    }
  }

}
  
  function filterData(countryData, value) {
  return countryData.filter((obj) => {
    return (
      obj.name.toLowerCase().startsWith(value.toLowerCase()) ||
      obj.code.toLowerCase().startsWith(value.toLowerCase())
    );
  });
}

function createButtonHtml(code, flag) {
  return `
    <div class="flag-icon flag-icon-${flag}"></div>
    <option class="cc-code" value="+${code}">+${code}</option>
  `;
}

function createNoDataHtml() {
  return '<li class="no-data-found">Sorry, No Data Found</li>';
}

const contactDiv = document.querySelector(".contact-frm-cc");

registerForm(contactDiv);
.cc-telcode {
  margin-bottom: 1em;
  display: flex;
  justify-content: center;
  width: 100%;
}

.cc-telcode div.cc-code,
.cc-list-items div.name {
  margin-left: 0.25em;
}
.cc-container {
  display: none;
  width: 300px;
  position: absolute;
}
.show-cc-list {
  display: block;
  z-index: +999;
}
.cc-data-list {
  max-height: 100px;
  list-style: none;
  margin: 1em 0 0 0;
  padding: 0;
  overflow-y: scroll;
  border: 1px soldi darkgray;
}
.cc-list-items {
  display: flex;
  padding: 0.25em;
  border: 1px solid lightgray;
}
.cc-list-items:hover {
  cursor: pointer;
  background-color: lightyellow;
}

.selected-country {
  cursor: pointer;
  background-color: rgb(73, 118, 241);
}

.contact-frm-cc {
  width: 100px;
}
<link rel="stylesheet" href="https://amitdutta.co.in/flag/css/flag-icon.css">

<div class="contact-frm-cc">
  <button class="cc-telcode">Tel code</button>
  <section class="cc-container">
    <input type="text" class="cc-search-box" placeholder="Search for country" />
    <ul class="cc-data-list">
    </ul>
  </section>
</div>

1

There are 1 best solutions below

0
Diego D On

If I understand correctly, and you need the dropdown options to show up gradually when the button is clicked, you might use the transition over the max-height property since it will suffice and won't require more complex keyframes.

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions

CSS transitions provide a way to control animation speed when changing CSS properties. Instead of having property changes take effect immediately, you can cause the changes in a property to take place over a period of time.

The strategy is having your list container with max-height: 0 by default when it has the slidein class. And when the class open is added, gradually turns to max-width: 100px (or any arbitrary size you'll choose for your dropdown options) with a velocity determined by transition: max-height 1s ease-in-out;.

.slidein {
  border: none;
  max-height: 0;
  overflow: scroll;
  transition: max-height 1s ease-in-out;
}

.slidein.open {
  max-height: 150px;
  border: solid 1px lightgray;
}

...

<section class="cc-container slidein">
    <input type="text" ...

Then with javascript you just toggle the class on the element when button is clicked:

container.classList.toggle('open');

The animation will apply also when the class is removed, thus the size will go back to zero smoothly when you click the button the second time.

I had to simplify your code to make a shorter answer and for example I used the template strategy to craft the dynamic option elements.

const countries = [
  { name: "Afganistan", code: "93", flag: "afg" },
  { name: "Albania", code: "355", flag: "alb" },
  { name: "Algeria", code: "213", flag: "dza" },
  { name: "American Samoa", code: "1-684", flag: "asm" },
  { name: "Andorra", code: "376", flag: "and" },
  { name: "Angola", code: "244", flag: "ago" },
  { name: "Anguilla", code: "1-264", flag: "aia" },
  { name: "Antarctica", code: "672", flag: "ata" },
  { name: "Antigua and Barbuda", code: "1-268", flag: "atg" },
  { name: "Argentina", code: "54", flag: "arg" },
  { name: "Armenia", code: "374", flag: "arm" },
  { name: "Aruba", code: "297", flag: "abw" },
  { name: "Australia", code: "61", flag: "aus" },
];

const container = document.querySelector('.cc-container');
const targetlist = document.querySelector('.cc-data-list');
const template = document.getElementById('countryTemplate');

countries.forEach(country => pushCountry(targetlist, template, country));

document.querySelector('.cc-telcode')
  .addEventListener('click', event => {
    container.classList.toggle('open');
  });

function pushCountry(target, template, country) {
  const newItem = template.content.firstElementChild.cloneNode(true);
  target.append(newItem);
  const {name, code, flag} = country;
  newItem.dataset = { ...newItem.dataset, ...country};
  newItem.querySelector('.flag-icon').classList.add(`flag-icon-${flag}`);
  newItem.querySelector('.name').textContent = `${name} (+${code})`;
}
.contact-frm-cc,
.cc-container {
  width: fit-content;
}

.cc-data-list{
  list-style: none;
  margin: 0;
  padding: 0;
}

.cc-data-list li{
  border-bottom: solid 1px lightgray;
  padding: .25em 0.5em .25em .25em;
}

.cc-data-list li:last-child{
  border-bottom: none;
}

.cc-data-list li:hover {
  cursor: pointer;
  background-color: lightyellow;
}

.cc-search-box{
  width: -moz-available;
  box-sizing: border-box;
  margin: .5em 1em .5em .5em;
  padding: .5em;
}

.flag-icon {
  width: 20px;
  border: solid 1px lightgray;
  display: inline-block;
}

.flag-icon + * {
  display: inline-block;
}

.slidein {
  border: none;
  max-height: 0;
  overflow: scroll;
  transition: max-height 1s ease-in-out;
}

.slidein.open {
  max-height: 150px;
  border: solid 1px lightgray;
}

button {
  cursor: pointer;
}

.cc-telcode{
  margin-bottom: 1em;
  padding: .5em 1em;
}
<link rel="stylesheet" href="https://amitdutta.co.in/flag/css/flag-icon.css">

<div class="contact-frm-cc">
  <button class="cc-telcode">Tel code</button>

  <section class="cc-container slidein">
    <input type="text" class="cc-search-box" placeholder="Search for country" />
    <ul class="cc-data-list">
    </ul>
  </section>
</div>

<template id="countryTemplate">
  <li
    class="cc-list-items"
    data-name="${name}"
    data-code="${code}"
    data-flag="${flag}">
    <div class="flag-icon"></div>
    <div class="name">${name} (+${code})</div>
  </li>
</template>