CSS tips to avoid Javascript

Posted

Recently I’ve been building a few web sites without JavaScript where possible, relying instead on modern browser support for CSS.

Here are some things I’ve figured out, including toggling the NavBar burger dropdown, smooth scrolling, showing and hiding modal dialogs with transitions!

Smooth Scrolling

In a previous post, Single Page Bulma template with Smooth Scroll and Scroll Spy JavaScript, I used JavaScript to override the on-click behaviour simply to use smooth scrolling. Something like these two methods:

<a id="top" href="#" data-target="about">Top</a>
<a id="jump" href="#element" data-target="element">Jump</a>
<section id="element"></section>
<script>
document.getElementById("top").addEventListener("click", e => {
  e.preventDefault();
  window.scroll({ behavior: "smooth", left: 0, top: 0 });
});
document.getElementById("jump").addEventListener("click", e => {
  e.preventDefault();
  window.scroll({
    behavior: "smooth", 
    left: 0,
    top: document.getElementById(e.target.dataset.target).getBoundingClientRect().top + window.scrollY 
  });
};
</script>

With CSS, most browsers support scroll-behaviour), so all I need is this style (not on the body element mind):

html {
  scroll-behavior: smooth;
}

Notable exceptions being all versions to-the of IE and Safari... in which case, the JavaScript method above would not do anything either!

Navigation Burger Drop Down

A really interesting trick with a checkbox, a label and CSS (which everybody knows except me, apparently):

  • The trick is to link a checkbox control to a label,
  • Such that, when the label is clicked (in my example below, a NavBar burger),
  • It will trigger CSS on the checkbox :checked selector state to hide or show something (in my example below, a NavBar drop down menu)

The minimum code to do this for a Bulma NavBar is:

  • Use the label element for the class navbar-burger instead of a.
  • Add an input element of type checkbox, giving it a unique ID, e.g. id="navbar-toggle", and making it hidden.
  • On the label element, add the attribute for="navbar-toggle" to refer to the checkbox above.
  • Finally, add CSS to trigger on :checked to display the navbar-menu.
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>NavBar Test</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
  <style>
    #navbar-toggle:checked + .navbar-menu { display:block; }
  </style>
</head>
<body>
  <nav id="navbar" class="navbar" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item">Logo</a>
      <label class="navbar-burger burger" for="navbar-toggle" role="button" aria-label="menu" aria-expanded="false">
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
      </label>
    </div>
    <input id="navbar-toggle" type="checkbox" class="is-hidden"/>
    <div class="navbar-menu">
      <div class="navbar-end">
        <a class="navbar-item">About</a>
        <a class="navbar-item">Help</a>
        <a class="navbar-item">Contact</a>
      </div>
    </div>
  </nav>
  <section class="section">
    <div class="container">
      <h1 class="title">Hello world!</h1>
    </div>
  </section>
</body>
</html>

Note that by not using the class is-active like in the standard Bulma example, you may have styles go unapplied!

Modal Dialog Opening

Continuing with Bulma, we now address modal overlay dialogs, forwhich I’ll use the modal card boilerplate, with simple JavaScript to activate (show) the modal, and deactive (hide) it.

<a id="launch-modal" class="button">Launch modal</a>
<div class="modal">
  <div class="modal-background"></div>
  <div class="modal-card">
    <header class="modal-card-head">
      <p class="modal-card-title">Modal title</p>
      <a class="delete" aria-label="close"></a>
    </header>
    <section class="modal-card-body">
      <h1 class="title">Hiya!</h1>
    </section>
    <footer class="modal-card-foot"></footer>
  </div>
</div>
<script>
document.getElementById("launch-modal").addEventListener("click", () => {
  document.getElementById("modal").classList.add("is-active");
});
document.getElementById("close-modal").addEventListener("click", () => {
  document.getElementById("modal").classList.remove("is-active");
});
</script>

And without JavaScript, the trick is in using a CSS to check for # anchors in the address using the :target selector. It’s quite straightforward and even a standard example at w3schools CSS :target Selector... in fact even gives an example for a tabbed interface which would work with Bulma too!

All you need is:

  • To show the modal, to click on a link with href pointing to the modal’s ID. This will trigger the CSS #modal:target which sets the modal display style.
  • And to hide it, to click on a close button with href pointing anywhere else. Unfortunately, using href="#" will scroll to the top of the page, which is not ideal! So, I just use a non-existent link. See CSS-Tricks On :target Fighting the Jump for options.
  • Similarly, clicking on the background would do the same.
<style>
#modal:target {
    display: flex;
}
</style>
<a id="launch-modal" class="button" href="#modal">Launch modal</a>
<div class="modal">
  <a class="modal-background" href="#x"></a>
  <div class="modal-card">
    <header class="modal-card-head">
      <p class="modal-card-title">Modal title</p>
      <a class="delete" aria-label="close" href="#x"></a>
    </header>
    <section class="modal-card-body">
        <h1 class="title">Hiya!</h1>
    </section>
    <footer class="modal-card-foot"></footer>
  </div>
</div>

Ah, again this won’t set the is-active class, so beware!

Modal Dialog Animation

Posta.re shared code on GitHub to apply various transition animations when showing a modal dialog, e.g. slide in, fade in, zoom in, 3d flip, etc.

Studying the styles for the slideTop effect, the minimum CSS required is as below. I’m using .modal with a .modal-card rather than .modal-content but either will do. Also, I’m not even bothering to apply any style (i.e. modal-fx-slideTop), but rather, overriding relevant modal styles directly.

.modal {
  display:flex;
  visibility:hidden;
}
.modal.is-active {
  visibility:visible
}
.modal .modal-card {
  transform:translateY(-20%);
  opacity:0;
  transition:all .3s
}
.modal.is-active .modal-card {
  transform:translateY(0);
  opacity:1;
}

Merging the last :target hack, and given that you know is-active is never applied (instead, use #modal:target), then the CSS I land up with is:

.modal {
  display:flex;
  visibility:hidden;
}
#modal:target {
  display: flex;
  visibility: visible;
}
.modal-card { 
  transform:translateY(-20%); 
  opacity:0; 
  transition:all 0.4s
}
#modal:target .modal-card { 
  transform:translateY(0); 
  opacity:1; 
  transition:all 0.4s
}

Conclusion

Putting it all together:

Try it all in action here

Voila! No JavaScript!