JavaScript MutationObserver calling on itself recursively - javascript

I am trying to use MutationObserver to Observe for style attribute changes in body element and change the overflow property of the style attribute of body to scroll.The style attribute changes when i make the browser window smaller .Firefox ends up crashing saying unresponsive script.Can anyone point out what wrong am i doing here.It seems to work properly in chrome though.
My code:
var target=document.body;
var Observer=new MutationObserver(function(mutations) {
mutations.forEach(function(mutationRecord){
document.body.style.overflow="auto";
});
});
Observer.observe(target,{attributes:true,attributeFilter:['style']});

I'd look to solve this at the root level: Why do different parts of your code want to fight over the document.body.style.overflow property?
But answering the mutation observer question:
If your goal is to ensure that document.body.style.overflow is set to "auto" regardless of changes made to it elsewhere, I'd only change it if it's not the value you want:
var Observer=new MutationObserver(function(mutations) {
if (document.body.style.overflow != "auto") {
document.body.style.overflow = "auto"
}
});
Observer.observe(document.body,{attributes:true,attributeFilter:['style']});
Also note that there's no need for the forEach loop.
At a guess, I'd say Firefox is firing the mutation observer even when the property hasn't actually changed, but Chrome isn't.
Side note: Again I'd say it would be better to find and deal with the fact your code seems to be fighting with itself. But as an alternative to a mutation observer workaround, here's a CSS workaround: An !important rule in a stylesheet overrides a normal inline style. So
body {
overflow: auto !important;
}
Now, that won't override document.body.style.overflow = "none !important", but it'll override any non-important rule, even an inline style one.

Your problem is on line:
document.body.style.overflow="auto";
Basically it creates an infinite loop which could surpass the Browser Javascript Stack size limit.
With your code you are observing the body dom, and when a change is made in body dom you actually change it style again, this goes forever.

Related

How to animate a button when it is visible for the user by scrolling

When you scroll on a page, the page shows an element, I want to be able to specify this element and
do code with it using JS, is this possible? I tried something but it had another problem..
What I tried was,
let section = document.getElementById('out');
window.onscroll = function() {
if (window.scrollY >= 678) {
document.getElementById('out').style.color = "red";
} else {
document.getElementById('out').style.color = "black"
}
}
I didn't use animate here, I just made sure it works, and it did, well almost, because if you zoom in/out it ruins it, I think that's because I got the 678 by going to the button and printing scrollY manually, is there anyway to make that automatic, so it works on any element I need?
I searched a lot and can't seem to find what I need, the solutions need jQuery, I need a solution only with html, css, and javascript.
In the future the solution will be css scroll timelines, but as that feature is at the time of writing experimental and is not supported by major browsers you can use intersection observers.
Quoted from MDN:
The Intersection Observer API lets code register a callback function that is executed whenever an element they wish to monitor enters or exits another element (or the viewport), or when the amount by which the two intersect changes by a requested amount.
To animate a component when it is in or out of view, you can give animated elements a .hidden class in your html markup and create an intersection observer which appends the .shown class to .hidden elements when they are in view.
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => entry.target.classList.toggle(“shown”, entry.isIntersecting))
})
const hiddenElements = document.querySelectorAll(“.hidden”)
hiddenElements.forEach((el) => observer.observe(el))
Then you can just apply transitions under a <selector>.shown css rule.

Why does Firefox fire a mouseenter event on page load?

When hovering over an element and then refreshing the page (without moving the mouse):
Chrome does not fire the mouseenter event on page load
Firefox does fire the mouseenter event on page load
Below is an example snippet. To reproduce the issue, hover over the div and then refresh the page. In Chrome, the div does not contain "mouseenter". In Firefox, it does.
Note that this does not work in the Stacksnippets environment since you need to click "run snippet" first. JSFiddle: https://jsfiddle.net/9fu6cx5d/7/
let div = document.getElementById('my-div');
div.addEventListener('mouseenter', function () {
div.innerHTML = 'mouseenter';
});
#my-div {
width: 150px;
height: 150px;
background-color: #aaaaaa;
}
<div id="my-div">
</div>
Which browser has the correct behaviour? How can I work around the difference in behaviour or at least make them both behave the same?
Chrome version: 59.0.3071.115 (Official Build) (64-bit)
Firefox version: 54.0 (64-bit)
As pointed out in the comments, Chrome's behavior is the correct one according to the specs. Below is an idea on how to work around the difference.
You can make sure you get the value right by checking whether the mouse is inside the bounds of the div on document load. Unfortunately there is no way in JS to check the mouse position without firing events, so you will have to resort to some hack involving CSS hover rules and checking against them on $(document).ready.
To quote this hilarious answer:
Overlay your page with a div that covers the whole document. Inside
that, create (say) 2,000 x 2,000 elements (so that the :hover
pseudo-class will work in IE 6, see), each 1 pixel in size. Create a
CSS :hover rule for those elements that changes a property (let's
say font-family). In your load handler, cycle through each of the 4
million elements, checking currentStyle / getComputedStyle() until
you find the one with the hover font. Extrapolate back from this
element to get the co-ordinates within the document.
N.B. DON'T DO THIS.
While you definitely shouldn't do this, the general idea of using non-effective hover styles for the sake of checking if an element is hovered without needing JS events is a good one if you just need to work around browser quirks. I'm using font-weight in the example below, but you can change it to whatever works for you.
The css
#my-div:hover {font-weight:700;}
The js
// Pseudocode!
var mouseIsInside = false,
div = $('#my-div');
$(document).ready(function(){
if (div.css('font-weight') === 700) {
mouseIsInside = true;
}
doStuffIfMouseInside();
});
div.on('mouseenter', function(){
mouseIsInside = true;
doStuffIfMouseInside();
})
function doStuffIfMouseInside() {
if (mouseIsInside) {
...
}
}
If you add (function(){})(); around your code it seems to work in both browsers.
It seems that firefox might be firing events before the dom is available causing problems with mousein/out events.
See: https://jsfiddle.net/9fu6cx5d/8/

How to detect resize of any element in HTML5

What should the best practices to listen on element resize event?
I want to re-position an element (jQuery dialog in my case), once it's size changed. But I am now more interested to do in a general way to to listen to resize event, unaware of how the resize happens. It suppose to be simple until I found an element can be re-sized by
window resize
content text changes
children elements or their children elements resized
a sibling element resize (e.g. a cell in a table)
JavaScript changes it src(of img)/style attribute directly (or it's child's)
JavaScript rewrite CSS rules or stylesheet
native resize feature textarea or CSS3 resize
browser's zoom or text-enlarge
CSS transition or animations (by :hover or any other mean)
In the de-facto standard, there is a event window.onresize to subscribe resize on a window/frame.
But there is no a standard event on the HTML content or DOM Elements.
I come across the following thought
DOM Level 3 event target only on window/document type
IE has onresize for Elements but it is IE only implementation
MutationObserver which replace Mutation Events, but it does not fit the need of "onresize"
naive JavaScript polling
MutationObserver is close(inner DOM changes), but it does not (yet) cross browser (IE10 does not support) and it generate noise, not CSS aware.
A naive JavaScript polling should work in all case, but it generate either delay or CPU waste of many poll.
As of July 2020, ResizeObserver is still un-official in W3C nor WhatWG but it is already supported by all major browsers since support Safari 13.1 since 2020-Mar-24.
FYI, there's a spec for a new ResizeObserver API. Chrome seems to be the only browser that has implemented it as of Aug 2018 (see caniuse.com), but there's at least one polyfill you can use now (which uses MutationObserver).
Yes there is not simple solution, that's not good.
I've found something very useful for this.: cross browser event based element resize
It's tricky, appending some needed html to the element that have to be listened and detects scrolling event.
Some html example from that page:
<div class="resize-triggers">
<div class="expand-trigger"><div></div></div>
<div class="contract-trigger"></div>
</div>
Also Some JS:
var myElement = document.getElementById('my_element'),
myResizeFn = function(){
/* do something on resize */
};
addResizeListener(myElement, myResizeFn);
removeResizeListener(myElement, myResizeFn);
But it works for elements those are able to have children, not for self-closing tags.
You can see the demo http://jsfiddle.net/3QcnQ/67/
Well, there is a easy library for that. Although there's nothing official how to listen on dimension changes of all types of elements and only window supports it at the moment we have luckily a polifill for that that works very accurate and supports all browsers even inclusive IE6+.
https://github.com/marcj/css-element-queries
You can find there a class ResizeSensor. To setup a listener on a element you can just do:
new ResizeSensor($('.myelements'), function() {
console.log('changed');
});
Given yourelement, when the size changes (ex. a text translation took place) you can doyourstuff(), including
ro.unobserve(yourelement);
var inilen = yourelement.offsetWidth;
var ro = new ResizeObserver( entries => {
for (let entry of entries) {
const cr = entry.contentRect;
if (inilen !== cr.width) {
doyourstuff();
}
}
});
ro.observe(<your element>);
In the future, we may have the luxury of the ResizeObserver everywhere, but for less recent browsers in 2021 we need to make do with a workaround. This article has already been posted, but it's pretty old and I think the solution might be overly complicated for modern browsers.
Still, the core idea remains: add an <object> element as a child with width: 100%; height: 100%;, and set a resize listener on its inner window object.
Here's some demo code that works in the latest Chrome and Firefox:
const div = document.getElementById('demo')
const obj = document.createElement('object')
obj.className = 'resize-detector'
obj.type = 'text/html'
obj.data = 'about:blank'
obj.addEventListener('load', function() {
// Initialize once.
handleResize()
// Add resize handler on the <object>'s inner window.'
obj.contentWindow.addEventListener('resize', function() {
handleResize()
})
})
div.appendChild(obj)
function handleResize() {
document.getElementById('size').innerHTML = `${div.offsetWidth}×${div.offsetHeight}`
}
.resizable {
/* Make this the offset parent of the <object> */
position: relative;
}
#demo {
width: 100px;
height: 100px;
background-color: #def;
/* Allow user resizing, for testing. */
resize: both;
overflow: hidden;
}
object.resize-detector {
display: block;
visibility: hidden;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
<div id="demo" class="resizable">
<div id="size"></div>
</div>
It doesn't work inside the StackOverflow snippet because of some same-origin policy shenanigans, but here's a JSFiddle with the same code.

JavaScript cssText not working as expected in Firefox and Opera browsers

Please consider the code snippet below:
<!DOCTYPE html>
<html>
<body>
<div id="mydiv">Sample Text</div>
<script type="text/javascript">
var md=document.getElementById("mydiv");
md.style.cssText="background-color:yellow !important;color:red;font-size:70px;font-weight:bold;";
setTimeout(function(){
md.style.backgroundColor="blue";
md.innerHTML+="<br/>Updated!";
},2000);
</script>
</body>
</html>
Explanation: I am trying out the cssText browser support and noticed that cssText is not working as per my expectation in Firefox, Opera browsers.
The above code defines "background-color:yellow !important" and after 2 seconds the background-color is changed to blue. But since I have specified '!important' in my cssText, I assume the background-color should not get updated. This works in IE, Chrome, Safari. But not in Firefox, Opera.
Can someone please suggest.
EDIT: I want to specify the !important rule for a css property and restrict further changes to it via javascript. I would like to achieve this using JavaScript i.e. specifying !important via JavaScript. Any help on this would be appreciated.
Stumbled across this question while googling for something else.
The first thing to understand, is that cssText doesn't create another style: It is merely a shorthand that lets you assign multiple styles at once. The following are roughly identical:
element.style.cssText = "background-color:yellow;color:red;";
element.style.backgroundColor = "yellow";
element.style.color = "red";
The only real thing to note, is that I believe that assigning to cssText will overwrite any existing element styles. Eg, the following will result in a style that is exactly equal to color:red; and not equal to background-color:yellow;color:red;. cssText effectively removes any existing element styles before applying the ones specified:
element.style.backgroundColor = "yellow";
element.style.cssText = "color:red;";
The second thing to realize, is that !important doesn't make a style read-only. It only prevents defined styles of higher specificities being used, and only does so as long as it is defined. When you assign the background color value of blue, you are effectively removing !important from the declaration. You'd have to set your background color as background-color:blue !important; in order to keep it.
In short, if something overwrites your background-color:yellow !important; with background-color:blue;, there's nothing you can do about it. Unless you do some other fancy work, like creating a timer interval that every X milliseconds resets the yellow !important style. But then you run into problems of having to keep track of the interval, especially if you may actually want to set the background color to some other value, otherwise it will just get overwritten on you!
var element = ...;
setInterval(1000, function(){
if (element.style.backgroundColor != "yellow !important") {
element.style.backgroundColor = "yellow !important";
}
});
The one thing I can think of, is if these styles are set in stone, you could just make them actual rules instead of inline element styles. You can keep the !important tag if you make it a rule:
.bg-yellow {
background-color: yellow !important;
}
element.className = "bg-yellow";
Here is your JS modified code. I've checked in FF 27 and it is working.
var md=document.getElementById("mydiv");
md.style.cssText="background-color:yellow !important;color:red;font-size:70px;font-weight:bold;";
setTimeout(function(){
md.style.setProperty="background-Color:blue"; //modified this line
md.innerHTML+="<br/>Updated!";
},2000);
Here is the Working Demo for you.
http://jsbin.com/kifozeka/2/edit

Opera: Altering img src attribute does not automatically update display?

When using javascript to swap images the HTML is updated fine but what Opera actually displays is not unless you scroll or resize the window. A picture of what happens when you scroll explains it best.
alt text http://img340.imageshack.us/img340/9455/87855188.png
Any ideas?
EDIT: The source of the problem seems to be that the image is inside a div that has float right.
EDIT2: This http://trac.dojotoolkit.org/ticket/3158 would suggest that it's a bug that was fixed and is back again.
Odd, I've never experienced problems like that before. I think that is a combination between browser and the graphics card / GUI, I've had exactly this behaviour before but in all sorts of applications (OpenOffice), not only the browser.
Ideas on how to maybe trick it into updating:
Set opacity to .99 and then back to 1
Change position by 1px (jerky though)
Set display to none and to block again (flickers, not nice, but to see whether it works)
Move it off the screen for a (milli)second and back again (probably flickers)
I have faced the same problem. This seems to be a bug related with Presto based Opera versions (< 12.5). The src attribute of the img elements seems to be updating correctly but the changes are not reflected to DOM. Triggering reflows are sadly not working. Only detaching and reattaching the node seems to fix the problem. I have tried following that led to no avail:
Change src to null, and then to new value,
Change src to null, change position (top/left etc), change width/height,
Trigger above with delay (i.e. 100ms delay between null and new value)
Performing various combination of above with any order.
The only way that correctly fixed the problem was detaching related node from DOM and reinserting. Here is the piece of code if anyone needs:
var isOperaPresto = this.navigator.userAgent.includes("Opera") && this.navigator.userAgent.includes("Presto");
if(isOperaPresto)
{
/* if browser is opera presto, updating image elements' sources will not upload the DOM visual.
So we need to do some hacking. Only thing that works is to remove and reAppend the relevant node... */
Object.defineProperty(HTMLImageElement.prototype, "src", {
enumerable: true,
configurable: true,
get: function() {
return this.getAttribute("src");
},
set: function(newSrc)
{
/*max-size confinement is required for presto if parent is display flex. Image will go out of its available size otherwise*/
this.style.maxHeight = this.style.height;
this.style.maxWidth = this.style.width;
this.setAttribute("src", newSrc);
/*we have to put this node back to exactly where we rip it from*/
var parent = this.parentNode;
if(this.nextElementSibling != null)
{
var reference = this.nextElementSibling;
parent.removeChild(this);
reference.insertAdjacentElement("beforebegin", this);
}
else
{
parent.removeChild(this);
parent.appendChild(this);
}
}
});
}

Categories

Resources