Close search results
Close search results

Removing jQuery

Because everyone likes vanilla

October 2nd, 2021

In 2006, jQuery was released. It was quite a game changer, providing a terse syntax that allowed for quick front-end development, resulting in scripts that behaved consistently across browsers - in a time when Internet Explorer 6 was still king.

Fast forward 15 years, and jQuery is used by millions of web sites - paradoxally, in a time when web developers since long have considered jQuery a legacy library of the past. The browsers have definitely come a long way since 2006, and many of the problems that jQuery set out to solve may not actually be problems anymore. Modern JavaScript natively provides much of the functionality jQuery offers, and IE6 and the likes are long gone. So as an exercise, why not try to replace jQuery with plain JavaScript (AKA Vanilla JS) and see if that promise has come true? I did so on this very website and had some interesting findings. Read on for more.

Hint: I will be referring to plain, native ES6 JavaScript as "Vanilla JS" below.

Selecting HTML elements

Maybe the most used feature of jQuery: Selecting elements by ID, class name, tag name, and so on. Example:

// jQuery
var myElements = $(".active");

The code above would return all elements that have the CSS class active. The dot (.) means we are selecting by class.

The native drop-in replacement would be:

// Vanilla JS
var myElements = document.querySelectorAll(".active");

Or, if you only need to select a single element:

// Vanilla JS
// Returns first match, or null if none was found
var myElement = document.querySelector(".active");

If we are selecting by class name, like above, we can also use (note that the dot is omitted):

// Vanilla JS
document.getElementsByClassName('active')

Selecting by ID is also just like jQuery, using the prefix "#":

// jQuery
var myElement1 = $("#myElement");

// Vanilla JS equivalent
var myElement2 = document.querySelector("#myElement");

// Also Vanilla JS equivalent
var myElement3 = document.getElementById("myElement");

A difference is that in jQuery, the returned elements will actually be jQuery objects, while the native methods all return objects of type HTMLElement. As long as we remove all jQuery code, this is fine, but if we need to convert an HTML element to a jQuery element (I hope not!), we can do the good old $(element).

Adding event handlers

Typically, we use jQuery.on() to attach event handlers. So if we want to add a handler that runs when a button with ID "myButton" was clicked, we can do this with jQuery:

// jQuery
$("#myButton").on("click", function() {
  console.log("Button was clicked");
}

Without jQuery, the equivalent code is:

// Vanilla JS
document.querySelector("#myButton").addEventListener("click", function() {
  console.log("Button was clicked");
}

It's more verbose, but does about the same thing. Not quite the same thing however, which brings us to delegated event handlers.

Event handlers with event delegation

If the element that we want to attach the event handler to does not exist yet, we can still attach handlers to it using this technique in jQuery:

// jQuery 
$("#dataTable tbody").on("click", "tr", function() {
  console.log("Row was clicked");
});

This requires the data table body to exist, but not any rows. When the table is clicked, jQuery checks if thetr (table row) element was involved and if yes, runs the handler. This means we can start with an empty table, fill with rows, and all event handlers will work as expected. We could also attach the event to the document body if even the table does not exist when the event handlers are attached.

We cannot do this natively, but a rather simple utility function does the trick (original source):

// Vanilla JS utility function for event delegation
function createDelegatedEventListener(selector, handler) {
  return function (event) {
    if (event.target.matches(selector) || event.target.closest(selector)) {
      handler(event);
    }
  }
}

We can then use the function like this:

// Vanilla JS
document.querySelector("#dataTable tbody").addEventListener("click", createDelegatedEventListener("tr", function() {
  console.log("Row was clicked");
});

So it works just fine - but I will admit, jQuery handles this really well and the native solution seems a bit clunky in comparison.

Adding the same event handler for multiple events

Sometimes we want to add the same event handler for when different events happen. For example, the code below adds an event handler that runs both when the user presses down the mouse button (typicall desktop computer), as well as when the user starts a touch (typically mobile device):

// jQuery
$("#myButton").on("touchstart mousedown", function(e) {
  console.log("Mouse button pressed or touch started");
});

While not as convenient, we can do this natively by a simple loop:

// Vanilla JS
for (const eventName of ["touchstart", "mousedown"]) {
  document.querySelector("#myButton").addEventListener(eventName, function(e) {
    console.log("Mouse button pressed or touch started");
  });
}

Adding and removing CSS classes

Adding and removing CSS classes in jQuery is easy. And doing it natively is just about as easy! Here we add, remove and toggle the class active on the element with ID "myButton":

// jQuery
$("#myButton").addClass("active");
$("#myButton").removeClass("active");
$("#myButton").toggleClass("active");

// Vanilla JS
document.querySelector("#myButton").classList.add("active");
document.querySelector("#myButton").classList.remove("active");
document.querySelector("#myButton").classList.toggle("active");

Hint: Both toggle methods accept an additional boolean parameter that adds the class if true, and removes the class if false.

Also, to check if an element has a certain CSS class, we can do this:

// jQuery
$("#myButton").hasClass("active")

// Vanilla JS
document.querySelector("#myButton").classList.contains("active")

Getting the size of the window

With jQuery, the (unit less) width and height of the current window can be retrieved by:

// jQuery
var width = $(window).width();
var height = $(window).height();

Getting these values natively is as easy and:

// Vanilla JS
var width = window.innerWidth;
var height = window.innerHeight;

Similarly, setting the width of an element with jQuery can be done like this:

// jQuery
$("#myElement").width("100px");

This can be replaced by:

// Vanilla JS
document.querySelector("#myElement").style.width = "100px";

AJAX/XHR requests

I have seen projects where jQuery is used for handling network requests and then not much else. In such cases, it might be attractive to look at the Vanilla JS alternative fetch, that can replace jQuery methods such as $.get, $.post, and $.ajax.

Here is a very simple GET request with no error handling:

// jQuery
$.get("/api/comments/1", function(data) { 
  console.log("Data retrieved from server.");
});

And the equivalent in Vanilla JS:

// Vanilla JS
fetch("/api/comments/1")
.then(function(response) { return response.json(); })
.then(function(data) {
  console.log("Data retrieved from server.");
});

Note that after we get the response, the body needs to be read with json() (assuming the response is JSON).

Sending a POST request to the server, with error callback:

// jQuery
$.post('/api/comments', data, function(result) {
  console.log("Data sent to server.");
}, 'json')
.fail(function(xhr) {
  console.log("Error sending data.");
});

In Vanilla JS, the code is a bit more involved. We both need a catch for handling network errors (such as timeouts) and check response.ok to make sure the return code is acceptable (we don't want server error codes such as 503!).

// Vanilla JS
fetch('/api/comments', 
{
  method: 'POST',
  body: JSON.stringify(data)
}).then(function (response) {
  if (!response.ok) {
    console.log("Did not get OK code from server.");
  } else {
    response.json().then(function(result) {
      console.log("Data sent to server.");
    });
  }
}).catch((error) => {
  console.log("Network error sending request.")
});

Fading

Fading out an element in jQuery is super easy:

// jQuery
$("#myElement").fadeOut(200, function() {
  console.log("Fade out complete.");
});

Without jQuery, we have a few different options. You might create a CSS class that animates the element and then remove it. We can also create a utility function that animates and then hides the element:

// Vanilla JS: Utility function that basically does what jQuery.fadeOut does.
fadeOut = function(element, duration, callback) {
  element.animate({
    opacity: 0
  }, {
    duration: duration,
    easing: "linear",
    iterations: 1,
    fill: "both"
  })
  .onfinish = function() {
    element.style.display = "none";
    if (callback) {
      callback();
    }
  }
}

// This is how we call the utility function.
fadeOut(document.querySelector('#myElement'), 200, function() {
  console.log("Fade out complete.");
});

Conclusion

I was pleasantly surprised to find that I could indeed remove jQuery without all too much hassle. Much of the functionality of jQuery has simple replacements (such as document.querySelectorAll), while other things should work with a minimal utility library (such as event delegation). Of course, the list above does not cover everything that jQuery offers and how to replace it, but in my case, this was all the information I needed to know.

Would I recommend removing jQuery just because it's possible? That depends. The minified version of jQuery is around 30kB at the moment and is unlikely to make much of a difference to load times on most sites. However, it could be a good idea to stop developing anything new with jQuery and look at other options first. For simpler systems, Vanilla JS might just be the right tool for the job.

Still, I kept jQuery as well as jQuery UI on the old Icon Maker page. While it matters little in my case, it still demonstrates maybe the biggest hurdle: If you have built a rather involved app using jQuery, and especially jQuery UI, migrating away from that may realistically mean that you need a rewrite. As always, your mileage may vary.

Hint: When switching from jQuery to Vanilla JS, first check Can I use... to make sure that the browsers that you are targeting support the functionality natively.

We can remove jQuery, just not with this call.
We can remove jQuery, just not with this call.

Leave a comment





This will just take a second.

Submitting your comment...
Page Theme: Dark / Light
Erik Moberg  2021