2

I am trying to create a search function in jQuery:

$('input').on('keyup', function(){
    var searchTerm = $("input").val().toLowerCase();
    $('.item').each(function(){
        if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1) {
            $(this).parent().show();
        } else {
            $(this).parent().hide();
        }
    });
});

Each time the user types in the input, it gets compared to the data attribute value of .item divs. If the data attribute of that element contains the search query, it gets displayed - otherwise hidden.

This works perfectly in Chrome, however it is really laggy in Safari for some reason when the user is typing.

Is there a way to fix this?

There are about 1400 divs (.item), and the data-text attribute is only around 10-20 characters for each element

Edit, fixed by removing .show() and .hide() - and replacing with native Javascript

trippyyyreddd
  • 131
  • 2
  • 9

2 Answers2

5

Solution

I have face similar issue before, I think you might want to try adding something called "debounce", which basically add a delay before doing any process. In the keyup case, it will wait for the user to stop typing for any set amount of time (let's say 0.5 second) and then do the process (searches or whatever) If you don't use debounce, it will do the search every single time the user trigger the keyup event.

You can search for articles on how to do debounce, I think there's a lot of them. But in essence it uses the setTimeout and clearTimeout function of JS

Here's from the first article I found: https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086

const debounce = (func, wait) => {
let timeout;

  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

How to use this function? simple, just add your actual function (the search function) as the first parameter, and the delay (microseconds) in the second parameter, and then use the .call() function (why do this? because the debounce will return a function). So I guess something like this:

$('input').on('keyup', function(){
    var searchTerm = $("input").val().toLowerCase();
    debounce(function(){
        $('.item').each(function(){
            if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1) {
                $(this).parent().show();
            } else {
                $(this).parent().hide();
            }
        });
    }, 500).call();
});

This is how I will do it, because then I can add some stuff outside of the debounce into the keyup event, but you can just put the debounce returned function into a variable and then bind it with the keyup (like in the article), or just straight up put the debounce inside the keyup, like this:

$('input').on('keyup', debounce(function(){
    ...
},500));

How does it works?

You can read them in the articles, or find answer in StackOverflow, here's what I got Can someone explain the "debounce" function in Javascript

But if I'm using my own words, basically what you first need to understand is setTimeout set a timer before a function is called, and clearTimeout cancel that timer. Now in the debounce you can see that there's a clearTimeout before any setTimeout. So every time the keyup event is triggered it will basically cancel the last timeout set (if any), and then it will set a new timeout. In essence, it will reset the timer to what you set every time the event is triggered.

So for example:

  1. The user want to search "abc"
  2. They type "a" -> the debounce set a timer of 500ms before calling the actual search of "a"
  3. Before the 500ms is up, the user type "b", so the debounce cancel that "a" search, and search for "ab" instead, while also setting a timer of 500ms before doing it
  4. Before the 500ms is up, the user type "c", so cancel the "ab" search, add a timer of 500ms to search for "abc"
  5. The user stop typing until 500ms is up, now the debounce actually call the search for "abc"

What this results to? The heavy processing for the search is only done once for "abc", you can also put a loader or something to this heavy processing so it looks better for the user

RonaldoC
  • 114
  • 5
  • Thanks, it does improve performance slightly. There is still some crazy lag when that function is called (even after the 500ms debouncer) – trippyyyreddd Jan 30 '21 at 03:44
  • Glad it help, then next is to improve the search function itself, see the other answer and you will find some suggestion – RonaldoC Jan 30 '21 at 06:48
0

Some quick fixes:

  • Collate the divs then show/hide in a single statement after the each rather than per iteration.

Changing the DOM is relatively expensive, so doing so in a single statement can greatly increase performance

  • If this is a table change to divs

Tables need to re-render the whole table on small changes. Fixed cell sizes can help. Not the case in this question, just a general improvement

  • Use an in-memory filter rather than read the DOM for each item.

Reading the DOM is much slower than in-memory (though in-memory uses more memory of course). For example, filter on .data() rather than [data-] as it will use in-memory. It's possible that this is quick in Chrome as Chrome may be caching the [data- attributes, so may not have an improvement in Chrome

  • debounce the input event so it only occurs when user has finished typing

Wait until the user has "finished" typing then run the action.

Although an edge case, this line

if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1)

will run the $(this).filter even when searchTerm.length < 1, change to

if (searchTerm.length < 1 || $(this).filter('[data-text *= ' + searchTerm + ']').length > 0)

Example showing this in action

function a() { console.log("a"); return true; }
function b() { console.log("b"); return true; }  // b() doesn't need to exist

if (a() || b()) console.log("c")
  • consider server-side paging / filtering

substantially reduces the "footprint" on the page, so will be much quicker/more responsive, but with a potentially slightly longer delay retrieving the data. Depending on how it's displayed, 1400 records may be a lot for the user to view in one go (hence your filtering).

freedomn-m
  • 27,664
  • 8
  • 35
  • 57