IntersectionObserver superseded by Proxy? - javascript

It makes sense to me that IntersectionObserver is preferred to adding scroll-based event listeners these days. Great.
However, I read here and here that a Proxy offers a more desirable way to do this and that Object.observe is perhaps even obsolete.
After reading about Proxy traps for a while, I still can't get my head around how I would use them in my imaginary use case below - or find any good references for it.
The imaginary use case:
Generate an arbitrary number of divs with random background colours.
Each div gets an IntersectionObserver.
When isIntersecting == true for any of the divs, the body's background colour changes to that of the div.
How would I even begin to think about implementing this using proxies?
Fiddle here.
It actually creates an attractive - if counterintuitive - effect.
let numberOfDivs = 5;
function createDiv(divNumber) {
// set the div's tag name, class name and a unique ID
let aDiv = document.createElement('div');
aDiv.className = 'my-divs';
aDiv.id = 'div' + divNumber;
// set a random hex bg colour for the div;
// drops a leading zero 1/16 of the time but it's not material to the example
aDiv.style.backgroundColor = '#' + Math.floor(Math.random()*16777215).toString(16);
// append the div to the body
document.body.appendChild(aDiv);
// set IntersectionObserver on the div
let observer = new IntersectionObserver(whatsIn, {threshold: 0.5});
observer.observe(aDiv);
}
// create the divs
for ( let i = 0; i < numberOfDivs; i++ ) {
let newDiv = createDiv(i);
}
// examine the IntersectionObserver output whenever isIntersecting changes
function whatsIn(payload) {
console.log("Is " + payload[0].target.id + " intersecting? " + payload[0].isIntersecting);
// change background color based on most recent isIntersecting == true
document.body.style.backgroundColor =
payload[0].isIntersecting
? payload[0].target.style.backgroundColor
: document.body.style.backgroundColor;
}

Related

Style elements through JS efficiently - best practice

I am building a tool for creating banners at a quicker and more streamlined way, instead of creating each individually.
Doing so, I have a number of different formats (such as 300x300px, 150x600px, 970x250px) and so forth.
The projects takes the container size as input, and should style and resize all elements based on the size.
Currently, it is very inefficient styling all properties individually.
An example is outlines below, however this list grows even longer for larger/more advanced banners with multiple elements.
To the question
What is the "best practice" way of styling a lot of different elements programatically, rather than setting each property individually through the style property?
As file size is key, any 3rd party libraries are not an option.
Current (inefficient) way of doing it
I am doing it this way to reduce file size, as I cannot have the styles for say 20 formats incorporated in the same file as it would grow too large.
let bannerSize = getBannerSize(window.innerWidth, window.innerHeight);
let firstFrame = document.getElementById("#frame-1");
let firstHeading = document.getElementById("#frame-1 h1");
let firstSubHeading = document.getElementById("#frame-1 h2");
let button = document.getElementById("#frame-1 button");
let secondFrame = document.getElementById("#frame-2");
if (bannerSize == 'square') {
firstFrame.style.top = 0;
firstFrame.style.left = 0;
firstFrame.style.width = '100%';
firstHeading.style.top = 50 + 'px';
firstHeading.style.left = 50 + 'px';
firstHeading.style.fontSize = 28 + 'px';
firstSubHeading.style.top = 120 + 'px';
firstSubHeading.style.left = 50 + 'px';
firstSubHeading.style.fontSize = 18 + 'px';
} else if (bannerSize == 'portrait') {
...
} else if (bannerSize == 'landscape') {
...
}
The output would then be as follows, where the same file can be used for multiple iframes and sizes:
One one way to handle this would be through a generic function such as :
function styleElem(selector, style){
(selector) ? selector.style.cssText = style : '';
return
}
It will apply inline styles to your adslots (much more easy to read/use programmatically)
Usage :
var square = document.getElementById("#frame-1")
styleElem(square , "width:300px;height:300px;margin:50px");//apply your custom css styles
var skyscraper= document.getElementById("#frame-2")
styleElem(skyscraper, "width:150px;height:600px;margin:50px");

Type Writter effect with HTML tags inside the text

I am trying to achieve typing effect which i did but the problem is that the text i have and is being typed inside a <p> tag as it's inner HTML has also some HTML tags like <span></span> and because the typing is char by char it gets typed out is <span> instead of rendered as the element. Is there any way to achieve the effect while keeping the HTML tags as they are? I have highlighted words that are wrapped in spans what i ultimately want to achieve is have the words typed out as highlighted...
function typeWriter() {
if (i < txt.length) {
document.getElementById("content_html").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, self.typing.speed);
}
}
typeWriter();
Here is an example of a text i am trying to type out :
<p>Surface chemistry deals with phenomena that occur at the surfaces or interfaces. Many important phenomena, noticeable amongst this being corrosion, electrode processes, heterogeneous catalysis, dissolution, and crystallization occur at interfaces. The subject of surface chemistry finds many applications in industry, analytical work, and daily life situations.</p>
It contains a <p> tag which can in the future change to any other HTML tag since this is coming from the server...
I don't fully understand your question, but you can also achieve a typing animation with pure CSS as shown in this Codepen which could remove some of the issues you are having.
animation: typing 7s steps(15, end), /* # of steps = # of chars */
blink-caret .5s step-end infinite alternate;
I belive you are looking for
document.getElementById("demo").innerText = "<p>asd</p>";
instead of innerHTML
This way it will render exacly "<p>asd</p>" instead of creating an element
Try This out Where I Am Rendering The Whole Text After You Have Appended Everything In It.
<script>
var i = 0;
txt = "<p>test this out</p>";
function typeWriter() {
if (i < txt.length) {
document.getElementById("content_html").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, 100);
} else {
document.getElementById("content_html").innerHTML = document.getElementById("content_html").innerText;
}
}
typeWriter();
</script>
So I wanted to make it myself because task is interesting. I know that #little_coder has already provide the best answer in comments to question.
This is what I have done:
js:
// text
txt = "asd<p>test this out<span>with more </span>and between text<span class='second'>second</span></p>afe";
//shared array
var instructions = [] ;
// typeWriter
var i = 0; //
var j = 0;
var elem = '';
var elem_value = '';
var speed = 50;
function typeWriter() {
if(j < instructions.length){
if (typeof instructions[j][1] == 'string'){
if (i < txt.length) {
instructions[j][0].innerHTML += instructions[j][1].charAt(i);
i++;
setTimeout(typeWriter, speed);
}else{
j=j+1;
i = 0;
setTimeout(typeWriter, speed);
}
}
else if(typeof instructions[j][1] == 'object'){
console.log("ins", instructions[j][0]);
instructions[j][0].appendChild(instructions[j][1]);
j=j+1;
i=0;
typeWriter();
}
}
}
//
// recreateNode
parser = new DOMParser();
function recreateNode(list, container){
doc = parser.parseFromString(list, "text/html");
doc.body.childNodes.forEach(function(a){
console.log(a);
if(a.nodeName == '#text'){
instructions.push([container, a.nodeValue])
}
else{ // if there is element to create
b = a.cloneNode(true); // handle deep elements
c = a.cloneNode(false); // this way I can get ONLY the element with attributes and classes
/* container.appendChild(c) */; // I append only element
instructions.push([container, c]);
recreateNode(b.innerHTML, c); // b will be appended to c
}
});
}
// init
parent = document.getElementById("content_html")
recreateNode(txt, parent);
typeWriter();
First I created recreateNode that creates instructions array that stores steps for recreating html structure of the text. Using this instructions in typeWriter I make typing effect or create html element. Keeping in instructions container value I know where to put next text.
Everything would be ok but when it comes to .appendChild or at least I think this is the cause... appending elements takes time that makes typing effect not being fluent.
js fiddle: https://jsfiddle.net/an5gzL7f/3/
Thanks in advance for any tips how it could work fast
I just got through a similar challenge while updating TypeIt (https://typeitjs.com) so that it can handle typing nested HTML. In short, the approach I used was repeatedly traversing over all the childNodes of the parent element, pulling out each nested node and expanding it into an array of queue items to be later typed.
It got a little hairy working through it all, but if it's helpful, you're welcome to search through the code to see how I'm doing it:
https://github.com/alexmacarthur/typeit/blob/master/src/helpers/chunkStrings.js#L98
A lot of it is very TypeIt-specific -- particularly how I construct objects whose properties contain the information needed to reconstruct those elements.
The most elegant solution is probably the one using the CSS only. However it comes with the downside, that if the text has a line break or is not written in a monospace font, it's basically unpracticable. I took the liberty to write a patch of code to accompany the rest of the solutions that are all useful in their own way.
The downside to my solution is that i used the textContent property, which will not render as HTML. I refuse to use innerHTML for security reasons. So adding <a> tags is probably not the best idea at the moment.
The goal of my approach is to use the window.requestAnimationFrame hook, because it's so powerful. And the rest of the code is still a bit cluttered but at least it's self explanatory.
let paragraphNode = document.querySelector('p');
let textObject = {
actionCounter: 0,
typeDelay: 6,
cursorDelay: 22,
cursorShow: false,
currentIndex: 0,
stringLength: paragraphNode.textContent.length,
textContent: paragraphNode.textContent
}
paragraphNode.textContent = "";
const writeText = ()=>{
textObject.actionCounter ++;
if( textObject.actionCounter % textObject.typeDelay == 0 ){
textObject.currentIndex += 1;
}
let string = textObject.textContent.substring(0, textObject.currentIndex);
if( textObject.actionCounter % textObject.cursorDelay == 0 ){
textObject.cursorShow = !textObject.cursorShow;
}
paragraphNode.textContent = (textObject.cursorShow) ? string + "|" : string;
window.requestAnimationFrame(writeText);
}
window.requestAnimationFrame(writeText);
Here is the codepen example i worked out. The only thing missing at the time of writing is a person brave enough to replace textContent with innerHTML and possibly check if the next letter is a '<' and add the whole tag at once. Might be tricky though. I strongly recommend against it.
typewriter on codepen

How do I remove variable randomly using jquery?

I'm trying to find a way to remove variable from certain div on the web using jquery. This does not involve using array. If I can do so with using fadeIn() or search() and remove(), that's even better.
var something = '#img' + count;
on the web, images will be added to div as time passes (using setTimeout). Those images have been assigned to variable (something) and I need to find a way to remove it from certain div on the web. It can be hide, remove, whatever, it has to disappear from user's view randomly (both time and which image will disappear).
Thanks for help and your time in advance.
my function code:
var count = 0;
function foo() {
var xPos = xPosition();
var yPos = yPosition();
var someTime;
$("div").append('<img id="Img" ' + count + ' src = "img.png" style="top:' + yPos + 'px; left: ' + xPos + 'px; " />');
var something = "#Img" + count;
someTime = setTimeout('foo()', randInterval());
$(something).hide();
count++;
if (timeRemaining == 0) {
clearTimeout(someTime);
return;
}
Give all the images a class. You can then use $(".class").length() to get the number of images, pick a random number in this range, and delete that element with .eq().
function addImage() {
var xPos = xPosition();
var yPos = yPosition();
$("div").append($("<img>", {
src: "img.png",
"class": "imageclass",
style: {
top: yPos+"px",
left: xPos+"px"
}
}));
setTimeout(addImage, randInterval());
}
setTimeout(addImage, randInterval());
function removeImage() {
var images = $(".imageclass");
if (images.length) {
var rand = Math.floor(Math.random() * images.length);
images.eq(rand).remove();
}
setTimeout(removeImage, randInterval());
}
setTimeout(removeImage, randInterval());
In my code I'm using separate timers for adding and removing images. If you prefer, you could remove the setTimeout from removeImage(), and just call it from addImage so it will always remove an image whenever it's adding a new one.
Please, never ever append a number to an id and piece together numbered names of things. It is unmaintainable and bad. Use class.
Assign a purpose or functionality to an element or elements by adding a class name to them. If you want to add information to an element, that is great, use data- prefix on the attribute name and it is all legal. data-itemid is an example.
You can query for matching elements with var those = $('.that-class-name'), stored for reuse. From there you can access individual elements using those.eq(0) through those.eq(x.length - 1). For example, if you somehow knew that the 3rd one needs to be removed, then those.eq(3).remove();. If you want to pick through them and only select ones that match a condition, use those.filter(callback).remove(), where callback returns true if the element referred to by this should be removed. If you want to filter those with another selector, .filter will accept a selector too.
Is that what you meant?

Javascript style.left is empty string

next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
pos = el.style[direction].split('px')[0];
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
I'm using the simple code above to try and move an element. Now when I breakpoint on this, the value of el.style[direction] is: " ". So then when i try to do anything with it, it breaks. Why would this be? Isn't style.left supposed to return an integer?
Why would this be?
Presumably because it hasn't been set to anything.
Isn't style.left supposed to return an integer?
No. It is supposed to return a string containing the value of the CSS left property as set directly on the element (either by setting the JS property itself or by using a style attribute). It does not get a value from the cascade and it should only be an integer if the value is 0 (since all other lengths require units).
See How to get computed style of a HTMLElement if you want to get the computed value for the property rather than what I described in the previous paragraph.
style provides the original style as calculated from the CSS, not the updated and possibly dynamic style. You probably want currentStyle instead.
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
var lft = document.defaultView.getComputedStyle(el)[direction];
pos = parseFloat(lft);
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
Note: like Elliot said you'll have to get the currentStyle/computedStyle. Here's a way to make it cross-browser, however when applying styles via JS, this is one good case where some sort of framework (eg Prototype [Scriptaculous], jQuery) would be useful.
Just a comment.
In your code:
> pos = el.style[direction].split('px')[0];
> pos = parseInt(pos, 10) + 10;
The split in the first line is superfluous, in the second line parseInt will convert (say) 10px to the number 10 just as effectively (and more efficiently) than what you have.
pos = parseInt(el.style[direction], 10);

infinite-loop via prepend element in DOM

Not Looking for a Use Framework XXX Answer
This question is not intended for finding a practical solution via a framework. Answering with use framework XXX, or this is so easy in framework XXX, or why not use this framework XXX??? doesn't answer the question.
I have a function meant to run after a page has been loaded: performShim. This function iterates over all elements in the DOM that are span tags, checks if they have a className of shim and if so, calls shim passing to it a reference of the matched element.
My goal was to prepend another span that contains an iframe to the element that is passed to shim.
With the code I wrote so far, I am able to append to the element's parent just fine. However, if I comment out the append line and instead try the prepend line the browser hangs in presumably an infinite-loop.
It's not readily obvious to me why this is the case.
function shim( element ) {
var iframe = document.createElement('iframe');
iframe.setAttribute( 'frameborder', '0' );
iframe.setAttribute( 'scrolling', 'no' );
iframe.setAttribute( 'align', 'bottom' );
iframe.setAttribute( 'marginheight', '0' );
iframe.setAttribute( 'marginwidth', '0' );
iframe.setAttribute( 'src', "javascript:'';" );
var span = document.createElement('span');
span.appendChild(iframe);
//element.parentNode.insertBefore(span,element); //causes infinite loop?
element.parentNode.appendChild(span); //this line works OK
var els = element.style;
var originalVisibility = els.visibility;
var originalPosition = els.position;
var originalDisplay = els.display;
els.visibility = 'hidden';
els.position = 'absolute';
els.display = 'inline';
var width = element.offsetWidth;
var height = element.offsetHeight;
els.display = originalDisplay;
els.position = originalPosition;
els.visibility = originalVisibility;
iframe.style.width = (width-6) + 'px';
iframe.style.height = (height-6) + 'px';
}
function performShim() {
var children = document.getElementsByTagName("span");
for( var i = 0; i < children.length; i++ ) {
if( children[i].className == "shim" ) {
shim(children[i]);
}
}
}
A NodeList (such as the one returned by document.getElementsByTagName) is typically a live list -- changes you make to the DOM show up in it as well. So each time you add a span before the current one, you're extending the list by one element and moving the current element over by one, and the next iteration puts you right back at the node you just finished.
You have a couple of easy workarounds for that...
Bump the counter when you add a node. (Ugly, and if you ever end up adding something instead of a span, you'll end up skipping nodes and it won't be obvious why.)
Copy the list to an array and iterate over the array. You could do this with something like
children = [].slice.call(children, 0); (more common) or
children = Array.apply(window, children);.
Use document.querySelectorAll, which returns you a NodeList that's not live. (And even if it were live, in this case you could select 'span.shim' and the inserted spans wouldn't show up in it anyway.)
Iterate backwards (from children.length - 1 to 0).

Categories

Resources