Astro Recipes
Essential CSS
GTM For Developers
JavaScript Roadmap
Learn JavaScript
Mastering Web Typography
Understanding Async JS
Unorthodox Tailwind
API Masterclass
Build and Deploy
The Beginner Freelancer
Real World Node
Profile Billing Logout
Learn JavaScript
Lesson Plan
  1. JavaScript and its ecosystem
    1. Welcome to Learn JavaScript!
    2. What is JavaScript used for?
    3. The JavaScript ecosystem
    4. Varying versions of JavaScript
  2. JS Basics
    1. Linking your JavaScript file
    2. Preparing your text editor
    3. The Console
    4. Comments
    5. On semicolons
    6. Strings, numbers and booleans
    7. Understanding Variables
    8. Understanding Functions
    9. The Flow of a Function
    10. Arrow functions
    11. Intro to objects
    12. If/else statements
    13. Comparing Objects
    14. The NOT operator
    15. Null and Undefined
    16. The BOM and the DOM
    17. Selecting an Element
    18. Changing Classes
    19. Listening to events
    20. Callbacks
  3. Building simple components
    1. How to think like a developer
    2. Starter files and Source codes
    3. Do this for every component
    4. 🛠 Off-canvas menu: Building an off-canvas menu
    5. 🛠 Modal: Building a Modal
    6. Lessons from the building process
    7. Debugging errors
    8. How to use a linter
  4. Arrays and loops
    1. Introduction to Arrays
    2. Array methods
    3. For loops
    4. The forEach loop
    5. Selecting multiple elements
    6. Nodes vs Elements
    7. 🛠 Accordion: Building an accordion
  5. Dom basics
    1. Id, classes, attributes, and tags
    2. Changing CSS with JavaScript
    3. Getting CSS with JavaScript
    4. Changing Attributes
    5. Finding an element's size and position
    6. DOM Traversals
    7. 🛠 Tabby: Building Tabby (A Tabbed component)
    8. 🛠 Carousel: HTML and CSS
    9. 🛠 Carousel: Switching slides with JavaScript
    10. 🛠 Carousel: Working the dots
    11. 🛠 Carousel: Positioning slides with JavaScript
  6. Events deep dive
    1. The listening element
    2. Default Behaviors
    3. Event propagation
    4. Event delegation
    5. Removing Event Listeners
    6. 🛠 Modal: Closing the modal
    7. 🛠 Accordion: Event delegation
    8. 🛠 Tabby: Event delegation
    9. 🛠 Carousel: Event delegation
  7. Transitions and Animations
    1. CSS Transitions
    2. CSS Animations
    3. Silky-smooth animations
    4. Integrating CSS transitions and animations with JavaScript
    5. Animating with JavaScript
    6. GreenSock Animation API (GSAP)
    7. 🛠 Off-canvas menu: Animations
    8. 🛠 Modal: Animating the modal
    9. 🛠 Modal: Animating the pointing hand
    10. 🛠 Modal: Animating the waving hand
    11. 🛠 Modal: Wave hand animation with JavaScript (using GSAP)
    12. 🛠 Accordion: Animations
    13. 🛠 Carousel: Animations
  8. Useful JS features
    1. Ternary operators
    2. AND and OR operators
    3. Early returns
    4. Template Literals
    5. Destructuring
    6. Default parameters
    7. Enhanced Object Literals
    8. Rest and Spread
    9. Useful array methods
    10. Looping through objects
    11. Returning objects with implicit return
    12. 🛠 Accordion: Using useful JavaScript features
    13. 🛠 Tabby: Using useful JavaScript features
    14. 🛠 Carousel: Useful JavaScript features
  9. JS Best practices
    1. Write declarative code
    2. Functions with a purpose
    3. Manage scope
    4. Reduce state changes
    5. Don't reassign
    6. Don't mutate
    7. Preventing Objects from mutating
    8. Preventing Arrays from mutating
    9. Write pure functions
    10. 🛠 Accordion: Refactor
    11. 🛠 Carousel: First refactor
    12. 🛠 Carousel: Refactoring the dots part
    13. 🛠 Carousel: Previous and next buttons
    14. 🛠 Carousel: Second refactor
  10. Manipulating text and content
    1. Changing Text and HTML
    2. Creating HTML Elements
    3. Adding multiple elements to the DOM
    4. Removing Elements from the DOM
    5. 🛠 Carousel: Creating dots with JavaScript
    6. 🛠️ Calculator: HTML and CSS
    7. 🛠️ Calculator: Happy Path
    8. 🛠️ Calculator: Testing the Happy Path
    9. 🛠️ Calculator: Easy Edge Cases
    10. 🛠️ Calculator: Difficult Edge Cases
    11. 🛠️ Calculator: Refactoring
    12. The switch statement
    13. 🛠️ Calculator: Refactoring (Part 2)
    14. 🛠️ Popover: Making one popover
    15. 🛠️ Popover: Making four popovers
    16. 🛠️ Popover: Making popovers with JavaScript
  11. Handling Forms
    1. Intro to forms
    2. Selecting form fields with JavaScript
    3. Form fields and their events
    4. Sanitize your output
    5. Generating unique IDs
    6. 🛠️ Popover: Dynamic ID
    7. 🛠️ Todolist: The HTML and CSS
    8. 🛠️ Todolist: Creating tasks with JavaScript
    9. 🛠️ Todolist: Deleting tasks with JavaScript
    10. 🛠️ Typeahead: The HTML and CSS
    11. 🛠️ Typeahead: Displaying predictions
    12. 🛠️ Typeahead: Selecting a prediction
    13. 🛠️ Typeahead: Bolding search terms
  12. Handling Dates
    1. The Date object
    2. Getting a formatted date
    3. Getting the time
    4. Local time and UTC Time
    5. Setting a specific date
    6. Setting a date with Date methods
    7. Adding (or subtracting) date and time
    8. Comparing Dates and times
    9. 🛠️ Datepicker: HTML and CSS
    10. 🛠️ Datepicker: Building the calendar
    11. 🛠️ Datepicker: Building the datepicker with JavaScript
    12. 🛠️ Datepicker: Previous and Next buttons
    13. 🛠️ Datepicker: Selecting a date
    14. 🛠️ Datepicker: Positioning the datepicker
    15. 🛠️ Datepicker: Showing and hiding
    16. Formatting a date with toLocaleString
    17. setTimeout
    18. setInterval
    19. 🛠️ Countdown timer: HTML and CSS
    20. 🛠️ Countdown timer: JavaScript
    21. 🛠️ Countdown timer: Counting Months
    22. 🛠️ Countdown timer: Daylight Saving Time
    23. 🛠️ Countdown timer: Counting Years
  13. Async JS
    1. Introduction to Ajax
    2. Understanding JSON
    3. The Fetch API
    4. Possible data types
    5. JavaScript Promises
    6. Requests and responses
    7. Sending a POST request
    8. Authentication
    9. Handling errors
    10. Viewing response headers
    11. CORS and JSONP
    12. XHR vs Fetch
    13. Using an Ajax library
    14. Reading API documentation
    15. Understanding curl
    16. 🛠️ Todolist: The Todolist API
    17. 🛠️ Todolist: Fetching tasks
    18. 🛠️ Todolist: Creating tasks
    19. 🛠️ Todolist: Editing tasks
    20. 🛠️ Todolist: Deleting tasks
    21. 🛠️ Todolist: Creating tasks with Optimistic UI
    22. 🛠️ Todolist: Handling Optimistic UI errors
    23. 🛠️ Todolist: Editing tasks with Optimistic UI
    24. 🛠️ Todolist: Deleting tasks with Optimistic UI
    25. 🛠️ Todolist: Refactor
    26. 🛠️ Typeahead: How to add Ajax
    27. 🛠️ Typeahead: Adding Ajax
    28. 🛠️ Typeahead: Handling errors
    29. 🛠️ Google Maps Clone: Creating your first Google Map
    30. 🛠️ Google Maps Clone: Fetching JSONP via JavaScript
    31. 🛠️ Google Maps Clone: Drawing directions
    32. 🛠️ Google Maps Clone: Driving directions
    33. 🛠️ Google Maps Clone: Handling errors
    34. 🛠️ Google Maps Clone: Adding stopovers
    35. 🛠️ Google Maps Clone: Refactor
  14. Advanced Async JS
    1. Requesting many resources at once
    2. Asynchronous functions
    3. Handling multiple awaits
    4. Asynchronous loops
    5. 🛠️ Dota Heroes: Listing heroes
    6. 🛠️ Dota Heroes: Filtering heroes (Part 1)
    7. 🛠️ Dota Heroes: Filtering heroes (Part 2)
    8. 🛠️ Dota Heroes: Refactoring
    9. 🛠️ Dota Heroes: Hero Page
    10. 🛠️ Dota Heroes: Making the hero page robust
    11. 🛠️ Dota Heroes: Heroes page refactor
  15. Handling Keyboard Events
    1. Keyboard users
    2. Handling commonly used keys
    3. Keyboard events
    4. Understanding Tabindex
    5. Detecting the focused element
    6. Directing focus
    7. Preventing people from tabbing into elements
    8. How to choose keyboard shortcuts
    9. Creating single-key shortcuts
    10. 🛠️ Off-canvas: Adding keyboard interaction
    11. 🛠️ Modal: Adding keyboard interaction
    12. 🛠️ Accordion: Adding keyboard interaction
    13. 🛠️ Tabby: Adding keyboard interaction
    14. 🛠️ Tabby: Refactoring
    15. 🛠️ Carousel: Adding keyboard interaction
    16. 🛠️ Carousel: Displaying help text
    17. 🛠️ Calculator: Adding keyboard interaction
    18. 🛠️ Popover: Keyboard
    19. 🛠️ Popover: Refactor
    20. Keyboard shortcuts with Command and Control modifiers
    21. 🛠️ Todolist: Keyboard
    22. 🛠️ Typeahead: Keyboard
    23. 🛠️ Typeahead: Selecting a prediction with the keyboard
    24. 🛠️ Google Maps Clone: Keyboard
    25. 🛠️ Dota Heroes: Keyboard
    26. 🛠️ Datepicker: Tabbing in and out
    27. 🛠️ Datepicker: Keyboard shortcuts
  16. Screen reader accessibility
    1. What is accessibility?
    2. How to use a screen reader
    3. Using NVDA
    4. Using Voiceover
    5. Aria roles
    6. Landmark roles
    7. Document structure roles
    8. Live region roles
    9. Widget roles
    10. Window and Abstract roles
    11. Accessible names and descriptions
    12. Hiding content
    13. ARIA properties and ARIA states
    14. ARIA for expandable widgets
    15. 🛠️ Off-canvas: Accessibility
    16. ARIA for modal dialogs
    17. 🛠️ Modal: Screen reader accessibility
    18. 🛠️ Accordion: Screen reader accessibility
    19. ARIA for Tabbed components
    20. 🛠️ Tabby: Screen reader accessibility
    21. 🛠️ Tabby: Refactor
    22. 🛠️ Carousel: Screen reader accessibility
    23. Roles that trigger Forms and Application modes
    24. What's next for accessibility?
  17. Handling Scroll
    1. The Scroll event
    2. 🛠️ Auto-hiding Sticky-nav: HTML and CSS
    3. 🛠️ Auto-hiding Sticky-nav: JavaScript
    4. 🛠️ Auto-hiding Sticky-nav: Natural reveal
    5. Intersection Observer API
    6. Intersection Observer Options
    7. 🛠️ Slide & Reveal
    8. 🛠️ Slide & Reveal: Always fade-in when you scroll down
    9. 🛠️ Slide & Reveal: Fine-tuning the animation
    10. 🛠️ Infinite Scroll: Anatomy
    11. 🛠️ Infinite Scroll: Infinite load
    12. 🛠️ Infinite Scroll: Refactor
    13. 🛠️ Infinite Scroll: Implementing the Infinite Scroll
  18. Mouse, Touch, and Pointer events
    1. Mouse Events
    2. 🛠️ Spinning Pacman: HTML and CSS
    3. 🛠️ Spinning Pacman: JavaScript
    4. Touch events
    5. Pointer events
    6. Touch-action
    7. 🛠️ Spinning Pacman: Supporting Touch
    8. Cloning elements
    9. 🛠️ DragDrop: HTML and CSS
    10. 🛠️ DragDrop: JavaScript
    11. 🛠️ DragDrop: Creating a drop preview
    12. 🛠️ DragDrop: Sortable drop preview
    13. 🛠️ DragDrop: Robustness
    14. 🛠️ DragDrop: Refactor
  19. Object Oriented Programming
    1. Before we begin
    2. What is Object Oriented Programming?
    3. Four Flavours of Object Oriented Programming
    4. Inheritance
    5. This in JavaScript
    6. Call, bind, apply
    7. Creating Derivative Objects
    8. Composition vs Inheritance
    9. Polymorphism
    10. Encapsulation
    11. Closures
    12. Encapsulation in Object Oriented Programming
    13. Getters and Setters
    14. What OOP flavour to use
    15. When to use Object Oriented Programming
  20. Writing reusable code
    1. Creating reusable code by writing libraries
    2. Two Types of libraries
    3. Including libraries with Script tags
    4. Including libraries with ES6 Modules
    5. Dynamic imports
    6. 🛠️ Off Canvas: Building a Library
    7. 🛠️ Modal: Library setup
    8. 🛠️ Modal: Opening the Modal
    9. 🛠️ Modal: Closing the modal
    10. 🛠️ Modal: Inheritance and Polymorphism
    11. 🛠️ Modal: Resolving differences between subclasses
    12. 🛠️ Modal: Exposing properties and methods
    13. 🛠️ Accordion: Building a library
    14. 🛠️ Tabby: Building a library
    15. 🛠️ Carousel: Building a library
    16. 🛠️ Calculator: Library
    17. 🛠️ Calculator: Fixing the Clear Key
    18. 🛠️ Calculator: Handling other keys
    19. 🛠️ Calculator: State
    20. 🛠️ Popover: Library
    21. 🛠️ Popover: Adding event listeners
    22. 🛠️ Typeahead: Library
    23. 🛠️ DatePicker: Library
  21. Going from Vanilla JS to JS Frameworks
    1. 🛠️ Building a Tiny framework
    2. 🛠️ Tiny: Add event listeners
    3. 🛠️ Tiny: Updating state
    4. 🛠️ Tiny: Rendering Child Components
    5. 🛠️ Tiny: Changing Parent State
    6. 🛠️ Tiny: Passing Props
    7. 🛠️ Tiny: Multiple Props
    8. 🛠️ Tiny: Passing values from sibling components
    9. 🛠️ Tiny: Mounting
    10. 🛠️ Tiny: Passing props to descendants
    11. 🛠️ Tiny: A tiny refactor
  22. Single Page Apps
    1. What is a Single Page App?
    2. Simple SPA using only CSS
    3. The Location Interface
    4. The History Interface
    5. Minimum viable server for a SPA
    6. 🛠️ Dota SPA: Introduction
    7. 🛠️ Dota SPA: Building The Heroes List
    8. 🛠️ Dota SPA: Building the filters
    9. 🛠️ Dota SPA: Filtering heroes
    10. 🛠️ Dota SPA: Displaying filtered heroes
    11. 🛠️ Dota SPA: Getting Ready to build the Hero Page
    12. 🛠️ Dota SPA: Building the hero page
    13. 🛠️ Dota SPA: Lore and abilities
    14. 🛠️ Dota SPA: Routing for Single-page apps

🛠 Accordion: Animations

9m:43s
Source code

Here’s what you’ll get by the end of this lesson:

Animated accordion.

Let’s start with the most important lesson of all.

Don’t use display none

You cannot animate an element if you hide it with display: none. The display property cannot be animated. Since display cannot be animated, we need a different way to hide the content.

First, we’ll remove display: none from the CSS code.

/* remove these properties */
.accordion__content {
display: none;
}
.accordion.is-open .accordion__content {
display: grid;
}

There are many ways to hide content. For example you can:

  • Push it off the screen (like Off-canvas menu)
  • Make it invisible (like Modal)

In this case, we will animate the height property. Animating height causes jank, but there are no other methods for animating an accordion.

First, we need to set height to zero.

.accordion__content {
height: 0;
}
Accordion spills over when height is set to zero.

And we have a problem. What we have looks broken:

  1. We can see a bit of the contents when the accordion is closed.
  2. The contents spill out of the accordion for “Beef”.

Contents actually spill out of every accordion. You only see contents spill out of “Beef” because I set position: relative to .accordion. If you remove the position: relative from .accordion, here’s what you’ll see.

Every accordion has contents that spill out of their containers.

To understand why this happens, we need to take a detour into CSS.

Calculating Height (and Width)

In CSS, every element is drawn with the CSS Box Model. The Box Model contains four things:

  1. The content itself
  2. Padding
  3. Border
  4. Margin
The box model

Normally, height values are calculated based on the box-sizing property:

  • If you set box-sizing to content-box (it’s a default value), height is the height of the content only
  • If you set box-sizing to border-box, height is the height of the content + any paddings and borders you have

To make things simpler, I’m going to explain the problem with height in terms of box-sizing: border-box from this point onwards.

Border box sizing.

Calculations for height change if you set height explicitly:

  1. If height is more than padding + border, height will be the value you set.
  2. If height is less than padding + border, browsers will set height to padding + border.

This makes more sense if we explain with an example. Let’s say you have the following HTML.

<div class="box">
<p>The quick brown fox jumps over the lazy dog</p>
</div>

You set the box-sizing property to border-box. You also add a background-color to the box.

.box {
box-sizing: border-box;
background-color: orange;
}

This is what you will see. In this case, the total height is the height of the content (118px).

Height of the content.

Let’s add a padding of 50px and border of 50px to the box. height of the box will be 318px. (50px + 50px + 118px + 50px + 50px).

.box {
box-sizing: border-box;
background-color: orange;
padding: 50px;
border: 50px solid brown;
}
Height of the box with padding and border.

If you set height to a value larger than padding + border, height will be the actual value you set.

In this case, I set height to 500px. Content expands to 300px so height of content + padding + border equals to 500px.

.box {
box-sizing: border-box;
background-color: orange;
padding: 50px;
border: 50px solid brown;
height: 500px;
}
Content expands to fill up extra height.

If you set height to a value smaller than padding + border, height will be padding + border.

In this case, I set height to 100px. But the computed height of the box is actually 200px.

.box {
box-sizing: border-box;
background-color: orange;
padding: 50px;
border: 50px solid brown;
height: 100px;
}
Stated height is lesser than computed height.

Here, content does not have any height at all. It is 0px tall.

You can still see the content because browsers set overflow to visible by default. This means: when content is smaller than it’s allocated amount, allow content to flow outside of the box and be visible.

You can see the content even if you set height, padding and border to 0.

.box {
box-sizing: border-box;
background-color: orange;
padding: 0;
border: none;
height: 0;
}
Content shows up even though height, padding and border are set to zero.

If you want to hide content that flows outside the box, you need to set overflow to hidden.

.box {
box-sizing: border-box;
background-color: orange;
padding: 0;
border: none;
height: 0;
overflow: hidden;
}
Content outside of the box gets hidden with overflow: hidden.

But here’s a strange thing with padding. If the box has padding, any content within the padding area is considered to be part of the box. overflow: hidden does not hide this part.

.box {
box-sizing: border-box;
background-color: orange;
padding: 50px;
border: none;
height: 0;
overflow: hidden;
}
Overflow: hidden does not hide the part inside padding.

In case you were wondering, overflow: hidden hides the part inside border.

.box {
box-sizing: border-box;
background-color: orange;
padding: 0px;
border: 50px solid brown;
height: 0;
overflow: hidden;
}
Overflow: hidden hides the part inside border.

Fixing the height issue

If you want to hide contents with height, you need to set height, padding, and border to zero. You also need to set overflow to hidden.

In this case, we don’t have border, so we only need to set height and padding to zero.

.accordion__content {
height: 0;
padding: 0;
overflow: hidden;
}
.accordion.is-open .accordion__content {
height: auto;
padding-right: 3em;
padding-bottom: 1.5em;
padding-left: 3em;
}
No more spillage!

But this solution is undesirable.

Why?

If you animate the accordion with this solution, you need to animate both height and padding properties. Both height and padding cause jank. Animating one of them is bad enough. Animating both of them at the same time will make the animation lag.

We want to reduce jank as much as we can. The ideal solution here is to animate height only.

If we want to animate only height, we need to adjust the HTML. Here, we can wrap the contents inside another <div>. We can put padding inside this <div>, so .accordion__content does not contain any more padding.

<div class="accordion__content">
<div class="accordion__inner">
<!-- The contents -->
</div>
</div>

Here are the required CSS changes:

/* Remove this */
.accordion__content {
display: grid;
grid-template-columns: 7.5em 1fr;
grid-column-gap: 1.5em;
align-items: center;
padding-right: 3em;
padding-bottom: 1.5em;
padding-left: 3em;
}
.accordion.is-open .accordion__content {
height: auto;
}
/* Replace with this */
.accordion__content {
height: 0;
overflow: hidden;
}
.accordion.is-open .accordion__content {
height: auto;
}
.accordion__inner {
display: grid;
grid-template-columns: 7.5em 1fr;
grid-column-gap: 1.5em;
align-items: center;
padding-right: 3em;
padding-bottom: 1.5em;
padding-left: 3em;
}

Quick Tip: You often need to rewrite the HTML to create animations. I wanted to show you this process so you don’t beat yourself up when you encounter the same problem!

Animating height

height can be animated, but you cannot animate height property to auto.

.accordion__content {
transition: height 0.3s ease-out;
}
.accordion.is-open .accordion__content {
height: auto;
}
No animation occurs.

You can only animate height if you set a specific value:

.accordion.is-open .accordion__content {
height: 200px;
}
Animating height from 0px to 200px

If you want to animate height, you need to use JavaScript to find (and set) the correct height for each accordion.

Getting the correct height

We can get the height of an element with getBoundingClientRect, but we need to know which element to get the height from.

In this case, we want to get height from .accordion__inner. The height remains accurate even if we set height on .accordion__content to zero.

.accordion__content {
height: 0;
}
.accordion.is-open .accordion__content {
/* Don't set any height property here yet */
}
The difference in height between accordion__content and accordion__inner.

To get the height from .accordion__inner, you need to traverse the DOM to find .accordion__inner.

Here, we know .accordion__content is the next element from .accordion__header. We also know .accordion__inner is the first element inside .accordion__content.

accordionContainer.addEventListener('click', event => {
const accordionHeader = event.target.closest('.accordion__header')
if (accordionHeader) {
// ...
const accordionContent = accordionHeader.nextElementSibling
const accordionInner = accordionContent.children[0]
}
})

We can get the height with getBoundingClientRect.

accordionContainer.addEventListener('click', event => {
const accordionHeader = event.target.closest('.accordion__header')
if (accordionHeader) {
// ...
const accordionContent = accordionHeader.nextElementSibling
const accordionInner = accordionContent.children[0]
const height = accordionInner.getBoundingClientRect().height
}
})

Setting the correct height

You can change height by setting the height property with JavaScript. Remember to add px because JavaScript height from getBoundingClientRect is a pixel value.

accordionContainer.addEventListener('click', event => {
const accordionHeader = event.target.closest('.accordion__header')
if (accordionHeader) {
// ...
const accordionContent = accordionHeader.nextElementSibling
const accordionInner = accordionContent.children[0]
const height = accordionInner.getBoundingClientRect().height
accordionContent.style.height = height + 'px'
}
})
Opening the accordion with animation.

Closing the accordion

To close the accordion, you need to set height back to zero. You need to do this because styles set by JavaScript are inline-styles. And inline styles have a higher specificity compared to a class in CSS.

Here, you use an if statement to check whether the accordion is currently open.

  • If the accordion is open, you need to close the accordion, so you set height to 0
  • If the accordion is closed, you need to open the accordion, so you set height to the value you got from getBoundingClientRect
accordionContainer.addEventListener('click', event => {
const accordionHeader = event.target.closest('.accordion__header')
if (accordionHeader) {
// ...
if (accordion.classList.contains('is-open')) {
accordionContent.style.height = height + 'px'
} else {
accordionContent.style.height = 0
}
}
})

Important note: The code above works only if classList.toggle comes after it.

// This works
if (accordion.classList.contains('is-open')) {
accordionContent.style.height = 0
} else {
accordionContent.style.height = height + 'px'
}
accordion.classList.toggle('is-open')
// This does not work
accordion.classList.toggle('is-open')
if (accordion.classList.contains('is-open')) {
accordionContent.style.height = 0
} else {
accordionContent.style.height = height + 'px'
}

Why? Because classList.toggle changes the HTML when you use it.

// Let's say accordion has `is-open` now
accordion.classList.toggle('is-open')
// aaccordion does not have `is-open` anymore. It was removed by classList.toggle.

To make the code clearer, you can calculate height upfront first.

let height
if (accordion.classList.contains('is-open')) {
height = 0
} else {
height = accordionInner.getBoundingClientRect().height
}
accordion.classList.toggle('is-open')
accordionContent.style.height = height + 'px'
Closing the accordion with animation.

In short, follow this general rule of thumb: Change the DOM only at the end (after you calculated everything you need).

A little aside

Remember to remove is-open from the HTML. We should start with the accordion closed.

<div class="accordion is-open">...</div>
<div class="accordion">...</div>
<div class="accordion">...</div>
<div class="accordion">...</div>

Note: If you want to start with accordions opened, you need to calculate height when the page loads.

Wrapping up

Three things:

  1. When you animate height, you create Jank. Sometimes there’s no choice. Either you animate or you don’t.
  2. Animate 1 property if you can. Don’t animate 2 (or more) properties that cause Jank.
  3. You might need to change the HTML to create smooth animations. If this is required, go for it.