UNPKG

emojiwall

Version:

On-screen emotes/emojis nicely animated, bubbling up from the bottom of the screen, to the top of the screen

204 lines (170 loc) 8.93 kB
var particle_list = document.getElementById('particles'); var emojiwallConfiguration = applyConfigurationDefaults(null); function applyConfigurationDefaults(forConfiguration=null) { return { 'canvasId': forConfiguration?.canvasId ?? 'particles', 'maxEmojis': Math.abs(forConfiguration?.maxEmojis ?? 300), 'maxCrossingTime': Math.abs(forConfiguration?.maxCrossingTime ?? 18), 'minCrossingTime': Math.abs(forConfiguration?.minCrossingTime ?? 6), 'maxEmojiSize': Math.abs(forConfiguration?.maxEmojiSize ?? 140), 'minEmojiSize': Math.abs(forConfiguration?.minEmojiSize ?? 60), 'effectRotation': { 'enabled': forConfiguration?.effectRotation?.enabled ?? false, 'maxRotation': forConfiguration?.effectRotation?.maxRotation ?? 25, } }; } function initializeEmojiwall(withConfiguration=null) { if(typeof withConfiguration !== 'object') { console.warn('Cannot initialize emojiwall. withconfiguration is not an object.'); return; } emojiwallConfiguration = applyConfigurationDefaults(withConfiguration); // Check that minimums are smaller than maximums emojiwallConfiguration.minCrossingTime = Math.min(emojiwallConfiguration.minCrossingTime, emojiwallConfiguration.maxCrossingTime); emojiwallConfiguration.minEmojiSize = Math.min(emojiwallConfiguration.minEmojiSize, emojiwallConfiguration.maxEmojiSize); particle_list = document.getElementById(emojiwallConfiguration.canvasId); // Y initial placement or emojis. updateStartTopStyle(); // If the container is resized, we need to update the initial Y position of the emojis, to make sure they spawn off screen. const canvasResizeObserver = new ResizeObserver(() => { updateStartTopStyle(); } ); canvasResizeObserver.observe(particle_list); if(particle_list === null) { console.warn(`Cannot initialize emojiwall. a container with Id "${withConfiguration.canvasId}" was not found.`); return; } console.debug('🎉 Successfully initialized emojiwall with settings', emojiwallConfiguration); } /* Set the initial top position of the emojis, below the bottom edge of the viewport */ function updateStartTopStyle() { // Remove the previously set style first document.querySelectorAll('.emojiwall-initial-style').forEach(style => style.remove()); // Calculate and set the new style const startTopPosition = document.getElementById(emojiwallConfiguration.canvasId).offsetHeight + (emojiwallConfiguration.maxEmojiSize / 4); const initialEmojiStyle = document.createElement('style'); initialEmojiStyle.classList.add('emojiwall-initial-style'); initialEmojiStyle.rel = 'stylesheet'; initialEmojiStyle.innerHTML = `.particles .emoji_item { top: ${startTopPosition}px; }`; document.head.appendChild(initialEmojiStyle); } function removeEmojiElement(elem) { elem.remove(); } const intersectionCallback = (entries) => { entries.forEach((entry) => { if (entry.isIntersecting === false) { removeEmojiElement(entry.target); } else { if (emojiwallConfiguration.effectRotation.enabled === true) { setTimeout(() => { rotateAnimation(entry.target); }, parseInt(entry.target.style.transitionDelay.replace('ms', '')) + 100); } } }); } const intersectionObserverOptions = { root: document.getElementById('particles'), rootMargin: '500px', threshold: 0.1 } const intersectionObserver = new IntersectionObserver(intersectionCallback, intersectionObserverOptions); function setRandomLeftPosition(element) { // Randomize start Y position element.style.left = getRandomNumber(0, 100) + '%'; } function getRandomNumber(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } function rotateAnimation(element) { const start_angle = element.style.transform.replace('rotate(', '').replace('deg)', ''); let end_angle = getRandomNumber(0, emojiwallConfiguration.effectRotation.maxRotation); // We want all rotations to cross the 0deg mark if (start_angle > 0) { end_angle *= -1; } const animation_rotation = [{ transform: `rotate(${start_angle}deg)` }, { transform: `rotate(${end_angle}deg)` }] // get transition duration for element const transition_duration = element.style.transitionDuration.split(',')[0].replace('s', ''); const rotation_duration = getRandomNumber(transition_duration * 1100, transition_duration * 1300); const animation_rotation_timing = { duration: rotation_duration, iterations: 1, } element.animate(animation_rotation, animation_rotation_timing); } function addEmojis(emojis, image_host) { // example 'emojis' array : // ['😀', '🎅', '/img/parrot.gif'] const new_emojis = []; emojis.forEach(function (element) { if (particle_list.childElementCount >= emojiwallConfiguration.maxEmojis) { // remove oldest emoji removeEmojiElement(particle_list.firstElementChild); } var particle_item = null; if (element.startsWith('/') === false) { // UTF-8 emojis // we wrap each emoji in their own element const emoji_span = document.createElement('span'); emoji_span.classList.add('emoji_item'); emoji_span.innerHTML = element; particle_item = emoji_span; } else { // Image emojis const emoji_image = document.createElement('img'); emoji_image.setAttribute('src', `${image_host}${element}`) emoji_image.classList.add('emoji_item'); particle_item = emoji_image; } // set emoji X start position setRandomLeftPosition(particle_item); particle_list.appendChild(particle_item); new_emojis.push(particle_item); }); setTimeout(function () { for (let i = 0; i < new_emojis.length; i++) { // Randomize 'end' Y position let left_position = getRandomNumber(0, 100); // If the new emoji spawned at the far right, make sure we don't // keep it leeching the right side: we force it to have a end X position // that is somewhat more left. This is to avoid emojis that stay partly // off screen during all their transition. // Same thing applies to the left side. const start_position = parseInt(new_emojis[i].style.left.replace('%', '')); if ( start_position > 90 || start_position < 0) { left_position = getRandomNumber(20, 80); } // Randomize speed! const duration = getRandomNumber(emojiwallConfiguration.minCrossingTime, emojiwallConfiguration.maxCrossingTime); // Randomize the emoji size const size = getRandomNumber(emojiwallConfiguration.minEmojiSize, emojiwallConfiguration.maxEmojiSize); // Randomize delay before the transition starts const delay = getRandomNumber(0, 5000); // Randomize easing function const easing_functions = ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear']; const easing = easing_functions[getRandomNumber(0, easing_functions.length - 1)]; // If the rotation effect is enabled, we need to add the rotation style const rotation_effect = emojiwallConfiguration.effectRotation.enabled ? `transform: rotate(${getRandomNumber(emojiwallConfiguration.effectRotation.maxRotation * -1, emojiwallConfiguration.effectRotation.maxRotation)}deg);` : ''; const common_style = `left: ${left_position}%; top: -${500+(emojiwallConfiguration.maxEmojiSize*2)}px; transition: top ${duration}s ${easing} ${delay}ms, left ${duration}s ${easing} ${delay}ms;${rotation_effect}`; // If the emoji is an image, we need to set the height of the element // (Setting the height on the span has side effects, that's why the styles are separate) const img_style = `height: ${size}px;`; // If the emoji is a span, we need to set the font-size of the element // (Not sure if setting the font-size on the image has side effects, but just in case) const span_style= `font-size: ${size}px;`; // Apply the appropriate styles new_emojis[i].style = common_style + (new_emojis[i].tagName === 'IMG' ? img_style : span_style); // Start observing the emoji, so we can remove it from the DOM when it's no longer visible intersectionObserver.observe(new_emojis[i]); } // Wait 50ms, so that the initial CSS has time to be applied before we apply the transition css }, 50); }