How can I list thousands/big list of emojis in a web browser without freezing the browser?

Viewed 575

I've looked on several questions, but none seem to solve my problem. The project needs to open a list of emojis inside a div (modal). So when the users press the emoji button, a modal with emojis will appear. If we list hundreds of emojis, then this is almost instantly done. If we put thousands of emojis, then it takes several seconds to load the modal.

I've provided a snippet with a demo array of emojis. Tried classic for approach and also createDocumentFragment (inspired from here), both of them having the same effect.

Looking for a solution that can render emojis one at a time, like in a queue. I know that I can push the DOM element in the queue and when it is loaded I can shift to the next element. The only thing is that I do not know when content.appendChild(button) or fragment.appendChild(button) is displayed. I've read that you can use setTimeout function, but I do not know what time to set or if it is recommended in this case.

Thank you!

function showEmojis() {
  let symbols = [
    "☺", "☻", "", "", "", "", "", "", "", "", "", "", "", "", 
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", 
    "" , "", "", "", "", "", "", "", "", "", "", "", "", "",
    "☹", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "☠", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "",  "", "", "", "", "", "", "", "", "", "", "", "", "", 
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", 
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "☕", "", "", "", "", "", "", "", ""]

    // Just to a have big array with emojis
    for (var i = 0; i < 6; i++) {
        symbols = symbols.concat(symbols)
    }

    let content = document.getElementById("content")

    // Simple for
    /*
    for(var i=0; i < symbols.length; i++) {
        let unicode = symbols[i]
        
        let button = document.createElement("button")
        button.classList.add("emoji-button")
        button.innerHTML = unicode
        
        button.onclick = function() {
            console.log("selected")
        }
        
        content.appendChild(button)
    }*/

    // Document fragment
    var fragment = document.createDocumentFragment()

    for (var i = 0; i < symbols.length; i++) {
        let unicode = symbols[i]

        let button = document.createElement("button")
        button.classList.add("emoji-button")
        button.innerHTML = unicode

        button.onclick = function() {
            console.log("selected")
        }

        fragment.appendChild(button)
    }

    content.appendChild(fragment)
}
.emoji-show-button {
    font-size: 15px;
}

.emoji-button {
    width: 28px;
    height: 28px;
    padding: 0px;
    margin: 5px;
    background: white;
    border-style: none;
    font-size: 20px;
}
<button class="emoji-show-button" onclick="showEmojis()">Show emojis</button>
<div id="content"></div>

3 Answers

This seems like an issue with the browser / os window manager struggling with emojis (Unicode)

A workaround would be photos, just like (https://emojipedia.org/emojipedia/)

Problem is you are running and creating buttons on the click event of Show emojis button this executes the same code everytime the user clickes the emogi button Some ways to optimise this would be

  1. Run this method in the window.Load
  2. Run this in an async method so this loads in the backround
  3. Hide the document fragment and only show it when the user click the button

also it would be easier not to set an onclick listener for each button but rather do something like this

<button id='button'> emoji</button>
<button id='button'> emoji2</button>

then in your js

document.addEventListener('click', (event) => {
if (event.target.id === 'button') {
    console.log(event.target.innerHtml)
}

You could create a helper that takes a fragment and appends its children to a container in chunks. By wrapping each call to add a chunk in setTimeout, other javascript stuff has the chance to run in between. E.g. if the user presses an emoji or other part of your UI, it will only wait for 1 chunk to be written before it responds.

You might want to investigate the overhead caused by the individual writes and check for an optimal chunk size. It might also make sense to create fragments per chunk first, and then only do 1 write to the DOM. I'll leave that up to you or an other answerer to find out :)

function appendInChunks(fragment, container, chunkSize = 5) {
  function writeChunk() {
    for (let c = 0; c < chunkSize && fragment.firstChild; c += 1) {
      container.appendChild(fragment.firstChild);
    }
  }
  
  for (let i = 0; i < fragment.children.length; i += chunkSize) {
    setTimeout(writeChunk);
  }
}

function showEmojis() {
  let symbols = [
    "☺", "☻", "", "", "", "", "", "", "", "", "", "", "", "", 
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", 
    "" , "", "", "", "", "", "", "", "", "", "", "", "", "",
    "☹", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "☠", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "",  "", "", "", "", "", "", "", "", "", "", "", "", "", 
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", 
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "☕", "", "", "", "", "", "", "", ""]

    // Just to a have big array with emojis
    for (var i = 0; i < 6; i++) {
        symbols = symbols.concat(symbols)
    }

    let content = document.getElementById("content")

    // Simple for
    /*
    for(var i=0; i < symbols.length; i++) {
        let unicode = symbols[i]
        
        let button = document.createElement("button")
        button.classList.add("emoji-button")
        button.innerHTML = unicode
        
        button.onclick = function() {
            console.log("selected")
        }
        
        content.appendChild(button)
    }*/

    // Document fragment
    var fragment = document.createDocumentFragment()

    for (var i = 0; i < symbols.length; i++) {
        let unicode = symbols[i]

        let button = document.createElement("button")
        button.classList.add("emoji-button")
        button.innerHTML = unicode

        button.onclick = function() {
            console.log("selected")
        }

        fragment.appendChild(button)
    }

    appendInChunks(fragment, content);
}
.emoji-show-button {
    font-size: 15px;
}

.emoji-button {
    width: 28px;
    height: 28px;
    padding: 0px;
    margin: 5px;
    background: white;
    border-style: none;
    font-size: 20px;
}
<button class="emoji-show-button" onclick="showEmojis()">Show emojis</button>
<button onclick="console.log('ping')">press to check browser response</button>
<div id="content"></div>

Related