Force browser to immediately repaint a dom element - javascript

I need to insert a huge html markup to some dom element which will take awhile. It is a reason why I want to display some preloader indicator. I have two blocks: #preloader and #container. Some code displays the preloader firstly and then starts to paste a big html markup.
The problem - preloader hasn't really displayed until browser will not finish render html markup. I've tried a lot of solutions (a lot of them are described here) but still haven't success.
An example is avalable below:
https://jsfiddle.net/f9f5atzu/
<div id='preloader'>Preloader...</div>
<div id='container'>Container...</div>
#preloader {
display: none;
background-color: #f00;
color: #fff;
hight: 100px;
text-align: center;
padding: 10px 20px;
}
#container {
background-color: #ccc;
}
setTimeout(function() {
// Define variables
let domPreloader = document.getElementById('preloader');
let domContainer = document.getElementById('container');
const html = Array(100000).fill("<div>1</div>");
// Display preloader
domPreloader.style.display = 'hide';
domPreloader.offsetHeight;
domPreloader.style.webkitTransform = 'scale(1)';
domPreloader.style.display = 'block';
// Render a big html
domContainer.innerHTML = html;
}, 1000);
Is there any solutions for the problem?

The way you did it, you're not releasing control to the browser between the display of the preloader and the display of the 'big html'.
Rather than encapsulating this whole block inside a setTimeout(), you should just differ the rendering part.
Please try something along those lines:
// Define variables
let domPreloader = document.getElementById('preloader');
let domContainer = document.getElementById('container');
// Display preloader
domPreloader.style.webkitTransform = 'scale(1)';
domPreloader.style.display = 'block';
// Render a big html
setTimeout(render, 100);
function render() {
const html = Array(100000).fill("<div>1</div>");
domContainer.innerHTML = html;
// Hide preloader
domPreloader.style.display = 'none';
}
JSFiddle

Related

Store Animation State When Coming Back To URL

I have a simple JS scroll event that when an element gets to within 50px of the top of the window the header animates and changes colour, which is done by using getBoundingClientRect().top < 50 on a trigger element. This functionality is only on the home page of the site.
Is there anyway of having it so when a user visits another URL/page on the site, and then comes back to this page via the browsers back arrow, that the previous animation state is still applied? If the page reloads and starts at the top again it doesn't matter, but if you click back to the page that uses this code, the menu transition happens even if you return to part of the page that was past the trigger point. I don't want to force the page to the top each time because this page is going to have downloadable and searchable info on, so that it would be real pain to be sent back to the top of that page each time.
I've given a working example below and via the CodePen link, the problem is of course on CodePen and StackOverflow when you go to a different URL and then click back to URL in question it actually reloads the page from scratch again, which doesn't happen as standard browser behaviour on day-to-day websites.
Codepen: https://codepen.io/anna_paul/pen/bGvPWRj
In that back end I'm using PHP, and I do have access to this is there needs to be a server side solution.
Any ideas or suggestions appreciated.
Note: On the actual site this scroll event is invoked via a debounce function, but I have removed this for code simplicity.
let triggerElement = document.getElementById('trigger-element'),
header = document.getElementById('h')
let menuChange = function() {
if(triggerElement.getBoundingClientRect().top < 50) {
header.style.background = 'black'
header.style.transition = '1s'
} else {
header.style.background = 'red'
header.style.transition = '.15s'
}
}
window.addEventListener('scroll', menuChange)
body {
margin: 0;
height: 200vh;
}
#h {
display: flex;
justify-content: center;
background: red;
color: #fff;
position: fixed;
top: 0;
width: 100%;
}
#trigger-element {
margin-top: 150px;
padding: 1rem;
background:blue;
color: #fff;
}
<header id="h">
<p>HEADER CONTENT</p>
</header>
<div id="trigger-element">Trigger Element</div>
I recommend using localStorage for this particular use case, because it can easily be implemented alongside your current method:
const triggerElement = document.getElementById('trigger-element');
const header = document.getElementById('h');
const animationTriggered = localStorage.getItem('animationTriggered') === 'true';
let initialLoad = true;
const menuChange = function() {
if (animationTriggered && initialLoad) {
header.style.background = 'black';
} else if (triggerElement.getBoundingClientRect().top < 50) {
header.style.background = 'black';
header.style.transition = '1s';
localStorage.setItem('animationTriggered', 'true');
} else {
header.style.background = 'red';
header.style.transition = '.15s';
localStorage.setItem('animationTriggered', 'false');
}
initialLoad = false;
}
window.addEventListener('scroll', menuChange);
This will remember the previous state and apply the black background color if the animation was previously triggered. This adds a small amount of overhead, but in a real-world scenario it should not have any noticeable impact on the performance of the application.

Invalid characters in HTML preventing my menu from toggling?

I'm creating a button that makes a table and a drop down menu. But the drop down menu doesn't work when it's clicked, like it should.
I keep receiving this error: 'Uncaught InvalidCharacterError: Failed to execute 'toggle' on 'DOMTokenList': The token provided ('[object HTMLCollection]') contains HTML space characters, which are not valid in tokens.' This error occurs near the last line here where I use toggle.
I've checked the HTML generated with JS and it seems fine. I just can't figure out what exactly this error is referring to. I've gone through the code at least a half dozen times and don't see any spaces within the html. This makes me question whether this is where the problem actually is. If anyone has a solution or insight into how to correct this, that would be much appreciated.
Here is the code:
//**HTML**
<div class = 'insertsGrid buttonStyle'>Grid</div>
//**CSS**
.buttonStyle{
width: 12vh;
height: 12vh;
border: 1px solid;
position: fixed;
display:flex;
justify-content:center;
align-items: center;
font-weight: 900;
font-size: 4vh;
color: #b8860b;
cursor:pointer;
background-color:white;
user-select: none;
}
th, td, tr {
border: 1px solid black;
}
//**Javascript**
const insertsGrid = document.getElementsByClassName('insertsGrid');
insertsGrid[0].addEventListener('mousedown', createGrid);
let z =0;
function createGrid (){
z++;
document.execCommand('insertHTML', true,/*this is the bar */'<div class=bar'+z.toString()+'></div>'+/*this is the menu options that show when bar is clicked */ '<div class =dropDownContent'+z.toString()+'><div class =dropDownContentX'+z.toString()+'><p>Add Row</p><p class=addColumn'+z.toString()+'>Add Column</p></div></div>'+/*this is the table */ '<table><tr><td>Head1</td><td>Head2</td></tr><tr><td></td><td></td></tr></table><br>');
let bar = document.getElementsByClassName('bar'+z.toString());
let dropDownContent = document.getElementsByClassName('dropDownContent'+z.toString());
let dropDownContentX = document.getElementsByClassName('dropDownContentX'+z.toString());
let addColumn = document.getElementsByClassName('addColumn'+z.toString());
//dom css for the html created in the dom
bar[0].style.width = '10%';
bar[0].style.height = '1%';
bar[0].style.border = '1px solid black';
bar[0].style.cursor = 'pointer';
bar[0].style.marginBottom = '50px';
dropDownContent[0].style.display = 'none';
dropDownContentX[0].style.display = 'inline';
dropDownContentX[0].style.backgroundColor = 'white';
dropDownContentX[0].style.width = '100%';
dropDownContentX[0].style.fontSize = '80%';
//action executed when the nav button is pressed
bar[0].addEventListener('mousedown' , tog);
function tog (){
dropDownContent[0].classList.toggle('dropDownContentX');
}
You are passing dropDownContentX into the toggle() method. This is a collection of DOM nodes, not a string. The toggle() method expects a CSS class name without spaces. You are passing the wrong data type to this method.
Source: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle

Javascript loading speed issue as variable increases

I am having an issue with page loading time. Currently right now I am running UBUNTU in Oracle Vm Virtual Box. I am using mozilla firefox as my browser and I am working on an etchasketch project from "The odin project".
My problem is the page loading time. The code takes a prompt at the start and generates a grid for the etch a sketch based on that prompt. I have not given it the minimum and maximum values (16 and 64) respectively, however any number when prompted at the beginning that is beyond 35 doesn't load or takes ages to load.
How do I speed up the process time? / why is it moving so slow? / how can I avoid this ? / is there a fix that I am over looking that can make this work a lot faster? / feel free to tackle any and all of those questions!
This is my HTML CODE:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8"/>
<title>
</title>
</head>
<body>
<div class="etchhead">
<p> Choose your grid size </p>
<input type = "text"></input>
<button id="startOver"> Clear Grid </button>
<p> Change color </p>
</div>
<div id="grid">
</div>
<script src="eas.js"></script>
</body>
</html>
And this is my CSS code:
p {
color: blue;
display: inline;
}
#grid {
display: grid;
width: 800px;
max-width: 800px;
height: 800px;
max-height: 800px;
line-height: 0;
}
.gridBox {
border: 1px solid black;
background-color: lightgrey
}
And this is my JAVASCRIPT code:
gridStart();
function gridStart(){
var boxes = 0
var selectBody = document.querySelector("#grid");
var addBox = document.createElement("div");
var boxCountStart = prompt("enter a number between 16 and 64");
var boxDimensions = (boxCountStart * boxCountStart);
function rowsAndColumns() {
var selectBody = document.querySelector("#grid");
var gridTemplateColumns = 'repeat('+boxCountStart+', 1fr)';
selectBody.style.gridTemplateColumns= gridTemplateColumns;
selectBody.style.gridTemplateRows= gridTemplateColumns;
};
function hoverColor(){
var divSelector = selectBody.querySelectorAll("div");
divSelector.forEach((div) => {
div.addEventListener("mouseover", (event) => {
event.target.style.backgroundColor = "grey";
});
});
};
rowsAndColumns();
for (boxes = 0; boxes < boxDimensions ; boxes++) {
var selectBody = document.querySelector("#grid");
var addBox = document.createElement("div");
addBox.classList.add("gridBox");
addBox.textContent = (" ");
selectBody.appendChild(addBox);
hoverColor();
};
};
There are two components to your issue. One is that you are repeatedly modifying the DOM in a loop. You can fix it by appending all your boxes to a DocumentFragment and then adding that to the DOM after your loop finishes. You are also calling hoverColor(); inside your loop which results in adding tons of event listeners that all do the same thing (since inside hoverColor you are adding a listener to every single div). You can fix both those issues like this:
var fragment = document.createDocumentFragment( );
for (var i = 0; i < boxDimensions ; i++) {
var addBox = document.createElement("div");
addBox.classList.add("gridBox");
addBox.textContent = (" ");
fragment.appendChild(addBox);
}
document.querySelector("#grid").appendChild( fragment );
hoverColor();
Here is a JSFiddle with your original code, and here is one with the modification.
You could also benefit from only having one event listener total. You don't need to loop and add an event listener to every div. Just add one to #grid and use event.target (like you already do, to find the div that the event originated from). Something like this:
function hoverColor(){
document.querySelector("#grid").addEventListener( 'mouseover', function ( event ) {
event.target.style.backgroundColor = "grey";
} );
}

How to sync printing of characters with sound?

I need to emulate what an old manual typewriter does when printing what is being typed on a web page. I want to develop JavaScript functions to pass it a string, and it would print out each character with a delay, and the sound file synced with each letter.
I'm new to JavaScript. What is the preferred method to do this? Should I be looking at jQuery for this? Or is this something simple to do?
I've seen problems with sound files being triggered like this on some web browsers, is there an audio file format which is best for this sort of thing?
I've found this, but the problem is, it doesn't work on all web browsers:
https://rawgit.com/mehaase/js-typewriter/master/example3-typewriter/index.html
You can try something like this:
// The delay between each keystroke
var delay = 300;
// The typewriter sound file url
var clickURL = "https://cdn.rawgit.com/halimb/res/6ffa798d/typewriter.wav";
// Get a reference to the container div
var container = document.getElementById("container");
var sampleString = "Hello world!";
//get a reference to the start button and typewrite onclick
var start = document.getElementById("btn");
start.onclick = function() { typewrite( sampleString ); };
function typewrite( str ) {
var i = 0;
container.innerHTML = "";
type();
function type() {
var click = new Audio( clickURL );
// This is triggered when the browser has enough of the file to play through
click.oncanplaythrough = function() {
click.play();
// Add the character to the container div
container.innerHTML += str[i];
i++;
if(i < str.length) {
window.setTimeout(type, delay);
}
}
}
}
* {
font-family: Courier;
font-size: 32px;
}
.btn {
display: inline-block;
border: 1px solid black;
border-radius: 5px;
padding: 10px;
cursor: pointer;
margin: 10px;
}
<div class="btn" id="btn">Start</div>
<div id="container"></div>
Update: on Safari. It seems the audio has to be triggered by a user event (e.g: onclick..), so I added a button, and made the typewriter start onclick.
The downside is that there's no way to pre-load the audio file, Safari make a server request and downloads the audio each time it is played. the only (dirty) way I could think of to overcome this is to provide a data URI instead of the audioURL.. you can try that if the playback speed really matters (this can be helpful: https://www.ibm.com/developerworks/library/wa-ioshtml5/)

Dynamically created divs not showing next to each other

I am dynamically creating divs and I want them to appear next to each other. I have the following code and after applying style to it (line 5) they keep showing one of top of the other. Please help.
rmc.onstream = function (e) {
if (e.isVideo) {
var uibox = document.createElement("div");
uibox.appendChild(document.createTextNode(e.userid));
uibox.className = "userid";
uibox.id = "uibox-" + e.userid;
uibox.style.cssText = 'display: inline-block; float: left';
document.getElementById('video-container').appendChild(e.mediaElement);
document.getElementById('video-container').appendChild(uibox);
} else if (e.isAudio) {
document.getElementById('video-container').appendChild(e.mediaElement);
}
else if (e.isScreen) {
$('#cotools-panel iframe').hide();
$('#cotools-panel video').remove();
document.getElementById('cotools-panel').appendChild(e.mediaElement);
}
};
Your styles are only being applied to uibox, and you need to apply them to emediaElement too:
if (e.isVideo) {
var uibox = document.createElement("div");
uibox.appendChild(document.createTextNode(e.userid));
uibox.className = "userid";
uibox.id = "uibox-" + e.userid;
uibox.style.cssText = 'float: left; width: 50%';
e.mediaElement.style.cssText = 'float: left; width: 50%';
document.getElementById('video-container').appendChild(e.mediaElement);
document.getElementById('video-container').appendChild(uibox);
}
Here is a working pen - I had to modify your code since I can't see where e.mediaElement is coming from, but you can get the idea: http://jsbin.com/woluneyixa/1/edit?html,css,js,output
If you're still having issues, please create a working codepen so we can see the problem you're having.
Also, using display: inline-block and float: left; is unnecessary; inline-block will have no effect whatsoever when using float.

Categories