Why chrome's rendering result is different when using breakpoints? - javascript

At first, i was trying to figuring how to implement a "line-by-line shows" effect, as far as i know, chrome will reflow the page when there is a dom insert action, so i just think the following code could work:
Click the "Click Me" span tag, there will be some lines of "-.-" span occur one-by-one.
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<span id="clickme">Click Me</span>
</div>
<div id="container"></div>
<script>
const container = document.getElementById("container");
const clickme = document.getElementById("clickme");
clickme.addEventListener("click", () => {
for (let i = 0; i < 10; ++i) {
const child = document.createElement("div");
for (let j = 0; j < 20; ++j) {
const span = document.createElement("span");
span.textContent = "-.- ";
child.appendChild(span);
}
container.appendChild(child);
}
});
</script>
</body>
</html>
Unfortunately, chrome seems to apply all the dom change in one batch, so i used performance recording to check if it really does, and here the result just verified the guess:
I was really confused by this result, so i tried again to add a breakpoint at the for-loop entry to see what happened in this process, and the result is completely different from the normal process:
Here i uploaded two screenshots because the rest ones is just the same render result (line-by-line).
So my question is why breakpoints change the behavior of chrome's rendering process, and is there any way to implement the "line-by-line shows" effect only with JavaScript?
(My original purpose is a little more complicated than "line-by-line", and cannot be solved by CSS transition)

When Chrome hits a breakpoint it will enter the special "spin the event loop" algorithm (or a variation of it at least).
This algorithm allows to pause the currently active task and let the event loop process other duties, such as updating the rendering. Note though that no other scripts will run while in this state. So you will have CSS animations update, new DOM changes painted to screen, but you won't e.g have scroll events, or requestAnimationFrame callbacks fire.
Also, note that the browser does not trigger a reflow at each DOM insertion. It will try to trigger a reflow only at the last moment when required (i.e right before painting in most cases). Though some Web-APIs can force such a reflow (e.g all the position getters like HTMLElement#offsetTop.

You can use setInterval as shown in your amended code below. This repeatedly interrupts the processing thread, those allowing the browser to refresh the display. When the number of desired lines is reached, you should invoke clearInterval.
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<span id="clickme">Click Me</span>
</div>
<div id="container"></div>
<script>
const container = document.getElementById("container");
const clickme = document.getElementById("clickme");
clickme.addEventListener("click", () => {
let lines = 0;
const intervalID = setInterval(() => {
const child = document.createElement("div");
for (let j = 0; j < 20; ++j) {
const span = document.createElement("span");
span.textContent = "-.- ";
child.appendChild(span);
}
container.appendChild(child);
if (++lines >= 10) {
clearInterval(intervalID);
}
}, 500);
});
</script>
</body>
</html>

Related

WebSpeechAPI to make a site accessible

WebSpeechAPI to make a site accessible but it first starts with the whole document and then the hovered element again.
How to resolve this?
I referenced most of the code from WebSpeechAPI MDN page.
All I want is for the browser to output the text of the tag that I am hovering over.
But it does that after speaking out all the contents of the document first. I think it does that because it catches the document first before I can reach the element.
var synth = window.speechSynthesis;
var inputForm = document.querySelector('form');
var inputTxt = document.querySelector('.txt');
var voiceSelect = document.querySelector('select');
var title = document.querySelector('#title');
var pitch = document.querySelector('#pitch');
var pitchValue = document.querySelector('.pitch-value');
var rate = document.querySelector('#rate');
var rateValue = document.querySelector('.rate-value');
var voices = []; //creat aan array to get thev voices
function populateVoiceList() {
voices = synth.getVoices(); // get the voices form the browser
for (i = 0; i < voices.length; i++) {
var option = document.createElement('option'); //create an element named option
option.textContent = voices[i].name + ' (' + voices[i].lang + ')'; //get all the info about the voice from the device and store in the text of the option tag
if (voices[i].default) {
option.textContent += ' -- DEFAULT';
}
option.setAttribute('data-lang', voices[i].lang); //set attributes of the option tag
option.setAttribute('data-name', voices[i].name);
voiceSelect.appendChild(option);
}
}
populateVoiceList();
if (speechSynthesis.onvoiceschanged !== undefined) { // this handler gets fired when the list returned by the getVoices function get changed
speechSynthesis.onvoiceschanged = populateVoiceList; //requires a function to handle the change in the list
}
document.onmouseover = function(e) {
var targ;
event.preventDefault(); //prevent default actions of the browser
if (e.target) targ = e.target;
var utterThis = new SpeechSynthesisUtterance(targ.textContent); //The SpeechSynthesisUtterance interface of the Web Speech API represents a speech request.
var selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name'); //get the data-name attribute of the selected option
for (i = 0; i < voices.length; i++) {
if (voices[i].name === selectedOption) {
utterThis.voice = voices[i]; //. We set the matching voice object to be the value of the SpeechSynthesisUtterance.voice property.
}
}
utterThis.pitch = pitch.value;
utterThis.rate = rate.value;
synth.speak(utterThis);
pitch.onchange = function() {
pitchValue.textContent = pitch.value;
}
rate.onchange = function() {
rateValue.textContent = rate.value;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="Speech sYNTHESIZER" >Speech synthesiser</h1>
<p>Enter some text in the input below and press return to hear it. change voices using the dropdown menu.</p>
<form>
<input type="text" class="txt">
<div>
<label for="rate">Rate</label><input type="range" min="0.5" max="2" value="1" step="0.1" id="rate">
<div class="rate-value">1</div>
<div class="clearfix"></div>
</div>
<div>
<label for="pitch">Pitch</label><input type="range" min="0" max="2" value="1" step="0.1" id="pitch">
<div class="pitch-value">1</div>
<div class="clearfix"></div>
</div>
<select></select>
</form>
</body>
</html>
All I want is for the browser to output the text of the tag that I am hovering over.
You are starting from a legitimately good intention, but in fact except if you are making a special game or an innovative interface, it's a bad idea.
Accessibility on the web simply doesn't work like this. You'd better try to conform to standards like WCAG to make your site accessible.
Several reasons for this, at least two big ones:
Finding elements to be spoken with the mouse is a possibility, but isn't the common way to navigate on the web.
Blind people generally don't use a mouse because they don't care about the placement of elements on the screen, can quickly get lost or miss important information that way. They just need to have them appear logically when navigating with tab, heading by heading or by another mean provided by the screen reader.
For partially sighted users, using the mouse to read elements below the cursor is of help or not depending on their vision, but for the same reasons as blind users, it's often just a complementary help; and screen reader software have the feature built-in.
Screen reader users have their preferences about language, voice, rate, pitch, etc. fortunately they don't need to set them for each site they visit
So, unless you are making something really special or new, prefer stick to largely used means to access your site.
But it does that after speaking out all the contents of the document first. I think it does that because it catches the document first before I can reach the element.
This is probably because of event bubbling.
I am not qualified to comment on the correctness of your accessibility attempt. Other answers are best for that.
Since you mentioned that the whole document is being read, I think it is because you are attaching mouseover event to the document at:
document.onmouseover = function(e) { ... };
With my knowledge and my ES6 syntax, I have written down the following code to actually select all individual tags instead of document.
const tags = document.body.querySelectorAll();
//Adds mouseover event listener to each tags in tags
for(let i=0; i<tags.length(); i++){
let text = tags[i].textContent;
tags[i].addEventListener('mouseover', function(text){
//add code to speak the 'text' variable here
});
}
Basically, I used querySelectorAll to get all the tags to tags array. Next, looped over each tag to extract the textContent for each tag. Finally added event listeners to each tag in tags to run the speaking function whenever mouseover is trigerred.
Hope it helps!
Get all elements in the body tag using pure javascript

JavaScript setTimeout 0 blocking page rendering?

According to this StackOverflow question
Changing the DOM is synchronous. Rendering the DOM actually happens after the JavaScript stack has cleared.
and according to this google doc the screen fresh rate 60fps is equivalent to roughly refreshing every 16ms, I write this example:
<!DOCTYPE html>
<html>
<head>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('#do').onclick = function() {
document.querySelector('#status').innerHTML = 'calculating...';
// setTimeout(long, 0); // will block
setTimeout(long, 1); // will not block
};
function long(){
let result = 0
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 1000; k++) {
result += i + j + k;
}
}
}
document.querySelector('#status').innerHTML = 'calculation done';
document.querySelector('#result').innerHTML = result.toString();
}
});
</script>
</head>
<body>
<button id='do'> Do long calc!</button>
<div id='status'></div>
<div id='result'></div>
</body>
</html>
with jsfiddle link
I played around with the code and found that blocking happens with time delay under 12ms, and happens more often with lesser delay.
I have two different ways of comprehension:
In this case only setTimeout with time delay over 16ms should not block, time delay of 0 and 1 are way less than 16ms so both of them should block;
Right after setTimeout call and long pushed to message queue (with optional delay), now the call stack is empty so in both cases setTimeout should not block and 'calculating...' always be rendered.
What's wrong with my understanding?
It's possibly to do with your browser throttling the delay.
It reads as though most browsers disregard a value of 0, might be worth looking at the DOM_MIN_TIMEOUT_VALUE for your browser.

how to force the browser to hide an element even if it is shown again after few seconds

Look at the following html code of the web page:
<!DOCTYPE html>
<html><head><script>
function hide()
{
var e = document.getElementById('test');
e.style.transition = 'visibility 0s,opacity 0s';
e.style.visibility = 'hidden';
e.style.opacity = '0';
e.style.transition = 'visibility 5s,opacity 5s';
show();
}
function show()
{
var e = document.getElementById('test');
e.style.visibility = 'visible';
e.style.opacity = '1';
}
</script></head><body>
<div id="test">Test</div>
<button type="button" onclick="hide()">go!</button>
</body></html>
If you open this html file in any standard browser (I tested in on Mozilla Firefox and Opera, under Windows 7) and click the button, nothing will happen (the text 'Test' will not vanish!).
I'm guessing that this is a matter of a kind of 'intelligence' of the browser. So, my question is: is it possible (for the above html code) to force the browser to hide the element?
You should not perform multiple changes to the same style property at once (transition and opacity in your case), as only the last values assigned to those properties will actually get an effect. The other value(s) are not processed by the browser -- that only happens when you give control back to the browser.
So only set the opacity to one value, the transition to one value, and then give control back.
Use setTimeout for this: it gives control back to the browser who will call you back when a certain time has elapsed.
function hide() {
var e = document.getElementById('test');
e.style.transition = 'opacity 0s';
e.style.opacity = 0;
setTimeout(showSlowly, 100); // This gives the browser time to perform the hiding action
}
function showSlowly() {
var e = document.getElementById('test');
e.style.transition = 'opacity 2s';
e.style.opacity = 1;
}
<div id="test">Test</div>
<button type="button" onclick="hide()">go!</button>
Read about Mutation Observers and how to react to specific DOM changes elegantly.
https://developer.mozilla.org/es/docs/Web/API/MutationObserver
Basically, you instantiate the MutationObserver object, setup the listeners and define the code to be run when a certain change in DOM appears.

How do I get this traffic Light sequence to work with the click of the button???

So, I can get it to go from red to amber, but I am stuck on how to further get it to change to green, back to amber and then red again. Any help would be highly appreciated.
Also, I have created this on dreamwaver.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Traffic Lights </h1>
<img id="light" img src="../../red.jpg">
<button type="button" onClick="changeLights()">Change Lights</button>
<script>
function changeLights(){
document.getElementById('light').setAttribute("src","../../amber.jpg")
document.getElementById('light').setAttribute("src","../../green.jpg")
document.getElementById('light').setAttribute("src","../../amber.jpg")
//document.getElementById('light').setAttribute("src","../../red.jpg")
}
</script>
</body>
</html>
The best solution is to put the image names into an array and the cycle through the array using a counter. That counter can increase or decrease the count depending on which "end" of the array we've last hit.
Also, don't use inline HTML event handling attributes (onclick, etc.) as they create "spaghetti code", they cause global wrapper functions that alter the this binding and they don't follow the W3C Event Model standard. Instead wire elements them up to event handlers via code using .addEventListener().
// Get references to DOM elements:
var img = document.getElementById('light');
var btn = document.getElementById('btn');
// Hook click of button up to event handling function
btn.addEventListener("click", changeLights);
// Put image names into an array:
var imgs = ["green.jpg" , "amber.jpg", "red.jpg"];
// Establish counter variable and directional variable
var count = 0;
var additive = 1;
function changeLights(){
// Set image by getting element from array based on current counter value
img.setAttribute("src","../../" + imgs[count]);
// Verification of action
console.clear();
console.log(img);
// When we hit the edges, reverse direction,
if(count === 2) {
additive = -1; // Go backward
} else if(count === 0) {
additive = 1; // Go forward
}
// Adjust the count accordingly
count+= additive;
}
<h1>Traffic Lights </h1>
<img id="light" img src="../../red.jpg">
<button type="button" id='btn'>Change Lights</button>

Defining a for loop in Javascript

I can't seem to define a for loop function, what am I doing wrong?
My HTML code:
<body onload="generate()">
My Javascript code:
function generate(){
for(i = 0; i < 150; i++) {
document.write("<div></div>");
}
};
Your loop is fine (other than that you don't declare i, and so you fall prey to the Horror of Implicit Globals), it's document.write that's the problem. You can only use document.write in an inline script, not after the page has been loaded (e.g., not in the body load event). If you use document.write after the page is loaded, it tears down the page and replaces it with what you output (because there's an implicit document.open call). So in your case, your page disappears and 150 blank divs are there instead.
To manipulate the page after load, you'll want to use the DOM, references:
DOM2 Core - Widely supported by browsers
DOM HTML bindings - Widely suppored by browsers
DOM3 Core - Fairly well supported, some gaps
For instance, here's how you'd write your generate function to append 150 blank divs to the page:
function generate() {
var i;
for (i = 0; i < 150; i++){
document.body.appendChild(document.createElement('div'));
}
}
Or more usefully, 150 divs with their numbers in:
function generate() {
var i, div;
for (i = 0; i < 150; i++){
div = document.createElement('div');
div.innerHTML = "I'm div #" + i;
document.body.appendChild(div);
}
}
Live copy
Separately, if you're going to do any significant DOM manipulation, it's well worth using a good JavaScript browser library like jQuery, Prototype, YUI, Closure, or any of several others. These smooth over browser differences (and outright bugs), provide useful utility functions, and generally let you concentrate on what you're actually trying to do rather than fiddling about with inconsistencies between IE and Chrome, Opera and Safari...
The document.write mentioned in https://stackoverflow.com/q/8257414/295783 is the reason it does not work. The first document.write wipes the page including the script that is executing.
A better way is
<html>
<head>
<script type="text/javascript">
var max = 150;
window.onload=function() {
var div, container = document.createElement('div');
for (var i=0;i<max;i++) {
div = document.createElement('div');
container.appendChild(div);
}
document.body.appendChind(container);
}
</script>
</head>
<body>
</body>
</html>
window.addEventListener("DOMContentLoaded", function() {
for(var i = 0; i < 150; i++) {
document.body.appendChild( document.createElement("div") );
}
}, false);
This should work, didn't test it though.
It's important to wait for the 'DOMContenLoaded' event, because else some elements might not exist at the time your script was executed.
do the folowing;
function generate(){
var echo="";
for(i = 0; i < 150; i++){
echo+="<div></div>";
}
document.getElementById("someID").innerHTML=echo;
//document.write(echo); //only do this, if you want to replace the ENTIRE DOM structure
};

Categories