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.
Related
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/
E.g. I have the following layout:
<div contenteditable="true">
<span class="text-block" contenteditable="false">
<span contenteditable="false">Name</span>
<a href="javascript:void(0)">
<i class="small-icon-remove"></i>
</a>
</span>
</div>
So, how to disable this:
and this:
I spent on this a lot of time myself, when trying to completely hide control selections (this is how they are called) in CKEditor's widgets. Unfortunately I don't have a good news.
Solution 1
First of all, there's a mscontrolselect event. When I found it (and the fact that its name has an ms prefix) I was very happy, because according to MS it should be preventable.
But it turned out that it's totally unstable. Sometimes it is fired, sometimes it isn't. It varies between IEs versions, DOM structure, attributes, which element you click, is it a block element, etc. The usual MS's crap. But you can try:
function controlselectHandler(evt) {
evt.preventDefault();
}
document.body.addEventListener('mscontrolselect', controlselectHandler);
However, this will completely block selection (if it worked). So you'll make those elements unselectable at all.
Solution 2
Then there's a second option, more reliable - moving selection somewhere else after such element was clicked. There are few ways this can be implemented. In CKEditor we're fixing selection on mousedown... and mouseup because (again) sometimes it's not enough for IE and it depends on dozen of conditions. You could also listen to selectionchange event and fix selection there.
However, again, we're also talking about blocking selection of such element.
Solution 3
Therefore, the third option is to block not selection, but the resizestart event. CKEditor combines this with enableObjectResizing command: https://github.com/ckeditor/ckeditor-dev/blob/a81e759/plugins/wysiwygarea/plugin.js#L211-L218. This solution will prevent resizing, but of course will not hide those ugly borders.
Solution 4
As I mentioned, I worked on this problem in CKEditor. We managed to make it possible to have non-editable elements inside editable, but with completely controllable and unified behaviour between browsers. The complete solution is too complex to be explained on StackOverflow and it took us months to implement it. We called this feature widgets. See some demos here. As you can see there are no control selection when non-editable element is selected. The selection appears on a short moment only between mousedown and mouseup, but only in specific cases. Except for that everything works as it would be native (although it's a completely fake thing).
Read more in the Introduction to Widgets and in the Widgets Tutorial.
This post was critical when solving this issue for me (works in tinyMCE):
How to Remove Resize handles and border of div with contentEditable and size style
By placing a contenteditable DIV within a non contenteditable DIV the handles do not appear in IE or FF but you can still edit the content
Ex.
<div class="outerContainer" contenteditable="false">
<div class="innerContainer" contenteditable="true">
</div>
</div>
Solution 5
When the focus is moved to child control change the content editable element attribute value to false and same way once your focus leaves from child control again set the content editable to true.
To disable the resize handles, all I had to do was add the following for IE11:
div {
pointer-events: none;
}
For firefox executing this line after the contenteditable element has been inserted works:
document.execCommand("enableObjectResizing", false, false);
What solved the problem for me was removing a max-width: 100% !important; line from the CSS properties of the DOM elements within the contenteditable DIV. Hope it helps!
BTW this does not happen on MS Edge... fingers crossed that this shows a movement in the right direction by MS :)
I had the same problem. It appears that from previous posts here there are certain behaviors that IE recognizes and will add this paragraph focus/resize. For me it was because I had a style for paragraphs within the contenteditible div.
Removing:
div[contenteditble="true"] p{
min-height:1em;
}
Fixed it for me.
SOLVED!
On placing the non content-editable span within a content-editable BODY, it started showing a resize-able SPAN container. What just fix my problem was a simple one-liner CSS style
pointer-events: none; on the inner SPAN tag.
min-width: 1.5cm;
display: inline-block;
pointer-events: none;
<body content-editable="true">
<span>Sample Text</span>
</body>
overflow:hidden also can cause this issue, like:
ul, ol {
overflow: hidden;
}
I have the same problem with CKEditor 4.4.7 in IE11. As a workaround, I save the current dimensions of an element on "mousedown" and set the "min-width", "max-width", "min-height" and "max-height" style properties to it's current dimensions. By that the element will be displayed in it's original size during resize. On "mouseup" I restore the style properties of the modified element. Here is my code:
$('textarea').ckeditor().on('instanceReady.ckeditor', function(event, editor) {
var $doc = $(editor.document.$);
$doc.on("mousedown", "table,img", function() {
var $this = $(this);
var widthAttrValue = $this.attr("width");
if (widthAttrValue) {
$this.data("widthAttrValue", widthAttrValue);
}
var widthStyleValue = this.style.width;
if (widthStyleValue) {
$this.data("widthStyleValue", widthStyleValue);
}
var width = widthStyleValue || widthAttrValue || String($this.width())+"px";
var height = this.style.height || $this.attr("height") || String($this.height())+"px";
$this.css({
"min-width": width,
"max-width": width,
"min-height": height,
"max-height": height,
});
$doc.data("mouseDownElem",$this);
}).on("mouseup", function() {
var $elem = $doc.data("mouseDownElem");
if ($elem) {
$elem.removeAttr("height").css("height","");
var widthAttrValue = $elem.data("widthAttrValue");
if (widthAttrValue) {
$elem.attr("width", widthAttrValue);
$elem.removeData("widthAttrValue");
} else {
$elem.removeAttr("width");
}
var widthStyleValue = $elem.data("widthStyleValue");
if (widthStyleValue) {
$elem.removeData("widthStyleValue");
}
$elem.css({
"min-width":"",
"max-width":"",
"min-height":"",
"max-height":"",
"width": widthStyleValue || ""
});
if (!$.trim($elem.attr("style"))) {
$elem.removeAttr("style");
}
$doc.removeData("mouseDownElem");
}
});
});
Here's what I did to fix this problem. For me this would only happen when the contenteditable element was empty and the resize handles would disappear when there was content so I created the following CSS only solution to go about this:
[contenteditable]:empty:after {
content: " ";
}
The idea behind the solution is whenever the contenteditable field is empty it applies a blank space pseudo element thus removing the resize tags from showing up when the user selects the contenteditable field. Once the user has entered anything then the pseudo element disappears.
Note, because of the use of pseudo elements, this fix only works on IE9 and up.
I had the same problem because I put CSS rules for the max-width onto all child elements within the contenteditable. Removing it or restricting it to images did the trick.
[contenteditable] * { max-width: 100%; } // causes the issue
[contenteditable] img { max-width: 100%; } // works fine for me
Make sure that no <p> elements are affected by the max-width property.
Nothing anyone else recommended here or in other threads really worked for me, but I solved it by doing:
[contenteditable="true"] p:empty {
display: inline-block;
}
This way the resize boxes disappeared, but I could still set my cursor below or in the P blocks to edit them.
I try to build a web application that will fit with almost all sizes of devices/browsers. To do so, my actual approach is to define,inside of my body, a div that will take the whole space of body:
#mydiv {
width: 100%;
height: 100%;
}
I calculate, then, width and height of my available space using:
var Width= $("#mydiv").width();
var Height= $("#mydiv").height();
I do what I want after. I position my elements with jQuery/CSS (percentages, top property, absolute positionnong,...), I draw with Rapahael.js....
I discovered that this approach is not always efficient, especially for browsers that display their addons as HTML. For example in my Chrome, when I install a toolbar addon, this toolbar is rendered in the page code source as HTML elements with their own styles (top=0, fixed postion..). The consequence is that all my work with top position is shifted by the height of the toolbar.
How can I calculate the net height of body?
What are alternative approaches to create webpage that adapts with the net browser size (I mean after any DOM injected elements outside of my control like ask.com toolbar... )?
Edit: so I gave this problem a little thought and I figure that if an add-on is going to draw to the DOM, it's most likely going to append itself to body. So, if you structured your document body in this manner:
<body>
<div id="container">
... all your content here
</div>
</body>
and the add-on inserted itself like this:
<body>
<div id="toolbar" style="margin:0;padding:5px;position:fixed;top:0px;left:0px;width:100%;height:20px;background-color:#000;color:#fff">toolbar</div>
<div id="container">
... all your content here
</div>
</body>
You could overcome this by setting #container's position to relative and adding the following script to your page:
var shift_amount = 0;
var children = document.body.children;
for(var i = 0; i < children.length; i++){
if(children[i].style.position == 'fixed'){
shift_amount += parseFloat(children[i].style.height);
}
}
var Height = $(window).height();
var Width = $(window).width();
if(shift_amount > 0){
// subtract fixed element height from available height
Height -= shift_amount;
}
As I'm pretty sure the question #RoryMcCrossan linked answers the question you asked, I will add that the preferred approach to creating responsive websites is to use media queries to adapt your CSS at various widths (mobile, tablet, desktop). Here is an example of media queries in action using Twitter Bootstrap, open that page and resize your browser window.
<style>
/* target only browsers less than or equal to 600px; */
#media (max-width: 600px) {
.sidebar {
display: none;
}
}
</style>
Regarding the issue of having toolbars and other components rendered in the HTML, this is going to be difficult to overcome as you can't know any and every element that will get injected into the DOM outside of your control. If you are targeting a specific use case, please point us to that add-on and there may be a solution to find.
I would take a look at the "mutation observers" to detect changes in the DOM structure.
Then you can just get those values again.
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
Width= $("#mydiv").width();
Height= $("#mydiv").height();
});
// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});
I have a page with a section to sketch a drawing in. But the touchmove events, at least the vertical ones, are also scrolling the page (which degrades the sketching experience) when using it on a mobile browser. Is there a way to either a) disable & re-enable the scrolling of the page (so I can turn it off when each line is started, but turn it back on after each is done), or b) disable the default handling of touchmove events (and presumably the scrolling) that go to the canvas the sketch is drawn in (I can't just disable them completely, as the sketching uses them)?
I've used jquery-mobile vmouse handlers for the sketch, if that makes a difference.
Update: On an iPhone, if I select the canvas to be sketched in, or just hold my finger for a bit before drawing, the page doesn't scroll, and not because of anything I coded in the page.
Set the touch-action CSS property to none, which works even with passive event listeners:
touch-action: none;
Applying this property to an element will not trigger the default (scroll) behavior when the event is originating from that element.
Note: As pointed out in the comments by #nevf, this solution may no longer work (at least in Chrome) due to performance changes. The recommendation is to use touch-action which is also suggested by #JohnWeisz's answer.
Similar to the answer given by #Llepwryd, I used a combination of ontouchstart and ontouchmove to prevent scrolling when it is on a certain element.
Taken as-is from a project of mine:
window.blockMenuHeaderScroll = false;
$(window).on('touchstart', function(e)
{
if ($(e.target).closest('#mobileMenuHeader').length == 1)
{
blockMenuHeaderScroll = true;
}
});
$(window).on('touchend', function()
{
blockMenuHeaderScroll = false;
});
$(window).on('touchmove', function(e)
{
if (blockMenuHeaderScroll)
{
e.preventDefault();
}
});
Essentially, what I am doing is listening on the touch start to see whether it begins on an element that is a child of another using jQuery .closest and allowing that to turn on/off the touch movement doing scrolling. The e.target refers to the element that the touch start begins with.
You want to prevent the default on the touch move event however you also need to clear your flag for this at the end of the touch event otherwise no touch scroll events will work.
This can be accomplished without jQuery however for my usage, I already had jQuery and didn't need to code something up to find whether the element has a particular parent.
Tested in Chrome on Android and an iPod Touch as of 2013-06-18
There is a little "hack" on CSS that also allows you to disable scrolling:
.lock-screen {
height: 100%;
overflow: hidden;
width: 100%;
position: fixed;
}
Adding that class to the body will prevent scrolling.
document.addEventListener('touchstart', function(e) {e.preventDefault()}, false);
document.addEventListener('touchmove', function(e) {e.preventDefault()}, false);
This should prevent scrolling, but it will also break other touch events unless you define a custom way to handle them.
The ultimate solution would be setting overflow: hidden; on document.documentElement like so:
/* element is an HTML element You want catch the touch */
element.addEventListener('touchstart', function(e) {
document.documentElement.style.overflow = 'hidden';
});
document.addEventListener('touchend', function(e) {
document.documentElement.style.overflow = 'auto';
});
By setting overflow: hidden on start of touch it makes everything exceeding window hidden thus removing availability to scroll anything (no content to scroll).
After touchend the lock can be freed by setting overflow to auto (the default value).
It is better to append this to <html> because <body> may be used to do some styling, plus it can make children behave unexpectedly.
EDIT:
About touch-action: none; - Safari doesn't support it according to MDN.
try overflow hidden on the thing you don't want to scroll while touch event is happening. e.g set overflow hidden on Start and set it back to auto on end.
Did you try it ? I'd be interested to know if this would work.
document.addEventListener('ontouchstart', function(e) {
document.body.style.overflow = "hidden";
}, false);
document.addEventListener('ontouchmove', function(e) {
document.body.style.overflow = "auto";
}, false);
I found that ev.stopPropagation(); worked for me.
To my surprise, the "preventDefault()" method is working for me on latest Google Chrome (version 85) on iOS 13.7. It also works on Safari on the same device and also working on my Android 8.0 tablet.
I am currently implemented it for 2D view on my site here:
https://papercraft-maker.com
this worked for me on iphone
$(".owl-carousel").on('touchstart', function (e) {
e.preventDefault();
});
the modern way (2022) of doing this is using pointer events as outlined here in the mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
Pointer events build on touchstart and other touch events and actually stop scroll events by default along with other improvements.
Is there a simple and reliable solution for detecting window vertical scrollbar appears/disappears?
window.onresize isn't triggered when after JavaScript DOM manipulation page becomes high enough for appearing scrollbar.
In this very similar post Detect if a page has a vertical scrollbar described solution how to detect whether scrollbar is present or not, but I need to know when exactly it appears.
Sorry to bring this back from the dead but I have just run in to this limitation and came up with my own solution. It's a bit hacky but stick with me ...
The idea is to add a 100% width invisible iframe to the page and listen for resize events on it's internal window. These events will pick up changes not only to the outer window's size but also when scrollbars get added to or removed from the outer window.
It triggers a regular window resize event so it requires no extra code if you are already listening for window resize.
Tested in IE9 and Chrome/Firefox latest - could maybe be made to work in older IEs but my project doesn't support those so I haven't tried.
https://gist.github.com/OrganicPanda/8222636
Based on OrganicPanda's answer, came up with this jquery thing
$('<iframe id="scrollbar-listener"/>').css({
'position' : 'fixed',
'width' : '100%',
'height' : 0,
'bottom' : 0,
'border' : 0,
'background-color' : 'transparent'
}).on('load',function() {
var vsb = (document.body.scrollHeight > document.body.clientHeight);
var timer = null;
this.contentWindow.addEventListener('resize', function() {
clearTimeout(timer);
timer = setTimeout(function() {
var vsbnew = (document.body.scrollHeight > document.body.clientHeight);
if (vsbnew) {
if (!vsb) {
$(top.window).trigger('scrollbar',[true]);
vsb=true;
}
} else {
if (vsb) {
$(top.window).trigger('scrollbar',[false]);
vsb=false;
}
}
}, 100);
});
}).appendTo('body');
This will trigger 'scrollbar' events on the window, if they appear/dissapear
Works on chrome/mac, at least. now, someone extend this to detect horizontal scrollbars :-)
The Scoop
It is possible to detect changes in scrollbar visibility by using ResizeObserver to check for changes in the size of the element that may take scrollbars and changes in the size of its contents.
Rationale
I started implementing a solution with the <iframe> method but quickly found that having a complete implementation required breaking the separation of concerns among the views of my application. I have a parent view which needs to know when a child view acquires a vertical scrollbar. (I don't care about the horizontal scrollbar.) I have two situations that may affect the visibility of the vertical scrollbar:
The parent view is resized. This is under direct control of the user.
The child view's contents becomes bigger or smaller. This is under indirect control of the user. The child view is showing the results of a search. The quantity and type of results determine the size of the child view.
I found that if I used <iframe> I'd have to muck with the child view to support the parent's needs. I prefer the child to not contain code for something which is purely a concern of the parent. With the solution I describe here, only the parent view needed to be modified.
So in looking for a better solution, I found this answer by Daniel Herr. He suggests using ResizeObserver to detect when a div's dimensions change. ResizeObserver is not yet available natively across browsers but there is a robust ponyfill/polyfill that I use for support in cases where native support is not available. (Here is the spec for ResizeObserver.)
Proof-of-Concept
I use this polyfill in its ponyfill mode. That way, the global environment remains untouched. This implementation relies on window.requestAnimationFrame, and will fall back on setTimeout for platforms that don't support window.requestAnimationFrame. Looking at the support for requestAnimationFrame on "Can I use...?", what I see there does not bother me. YMMV.
I have a live proof-of-concept. The key is to listen to changes in size on the DOM element that can accept scroll bars (the element with id container, in green) and listen to changes in size on the content that may need scrolling (the element with id content). The proof-of-concept uses interact.js to manage a resizer element (with id resizer, in blue) that allows resizing container. If you drag the bottom right corner of resizer, it will resize both resizer and container. The two buttons allow simulating changes in the size of the contents displayed by container.
I'm using this method in code that is currently at a pre-release stage, meaning it passed tests on multiple browsers, and is being evaluated by stakeholders, but is not yet in production.
The HTML:
<!DOCTYPE html>
<html>
<head>
<script data-require="interact.js#*" data-semver="1.0.26" src="//rawgit.com/taye/interact.js/v1.0.26/interact.js"></script>
<script src="//rawgit.com/que-etc/resize-observer-polyfill/master/dist/ResizeObserver.global.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="resizer">
<div id="container">
<ul id="content">
<li>Something</li>
</ul>
</div>
</div>
<button id="add">Add to content</button>
<button id="remove">Remove from content</button>
<p>Scroll bar is: <span id="visibility"></span></p>
<ul id="event-log"></ul>
<script src="script.js"></script>
</body>
</html>
The JavaScript:
var container = document.getElementById("container");
var resizer = document.getElementById("resizer");
interact(resizer)
.resizable({
restrict: {
restriction: {
left: 0,
top: 0,
right: window.innerWidth - 10,
bottom: window.innerHeight - 10
}
}
})
.on('resizemove', function(event) {
var target = resizer;
var rect = target.getBoundingClientRect();
var width = rect.width + event.dx;
var height = rect.height + event.dy;
target.style.width = width + 'px';
target.style.height = height + 'px';
});
var content = document.getElementById("content");
var add = document.getElementById("add");
add.addEventListener("click", function() {
content.insertAdjacentHTML("beforeend", "<li>Foo</li>");
});
var remove = document.getElementById("remove");
remove.addEventListener("click", function() {
content.removeChild(content.lastChild);
});
// Here is the code that pertains to the scrollbar visibility
var log = document.getElementById("event-log");
content.addEventListener("scrollbar", function () {
log.insertAdjacentHTML("beforeend", "<li>Scrollbar changed!</li>");
});
var visiblity = document.getElementById("visibility");
var previouslyVisible;
function refreshVisibility() {
var visible = container.scrollHeight > container.clientHeight;
visibility.textContent = visible ? "visible" : "not visible";
if (visible !== previouslyVisible) {
content.dispatchEvent(new Event("scrollbar"));
}
previouslyVisible = visible;
}
// refreshVisibility();
var ro = new ResizeObserver(refreshVisibility);
ro.observe(container);
ro.observe(content);
The CSS:
* {
box-sizing: border-box;
}
#container {
position: relative;
top: 10%;
left: 10%;
height: 80%;
width: 80%;
background: green;
overflow: auto;
}
#resizer {
background: blue;
height: 200px;
width: 200px;
}
If you're using AngularJS, you can use a directive to detect when the width changes (assuming the appearing/disappearing scrollbar is a vertical one):
app.directive('verticalScroll', function($rootScope){
return {
restrict: 'A',
link: function (scope, element) {
scope.$watch(
function() {
return element[0].clientWidth;
},
function() {
$rootScope.$emit('resize');
}
);
}
}
});
This fires an event on the root scope which other directives or controllers can listen for.
The watch is fired by the angular digest loop, so this relies on Angular having loaded/removed the extra content which has caused your scrollbar to appear/disappear.
Dynamically Detect Browser Vertical Scrollbar Event by
comparing window.innerWidth to getBoundingClientRect()
of a DIV element using Javascript. Tested with latest
IE FF Chrome. See documentation here
It's all about when you need to determine the scrollbar's visibility.
The OP speaks of a time "after JavaScript DOM manipulation". If that manipulation happens in your code, then that's the time for checking if the scrollbar is visible. Why do you need an event in addition to that? How is it that you don't know when this DOM manipulation occurs?
I realize this is an old question, but I'm just now dealing with this in a pure javascript project, and I have no issue knowing when to check for scrollbar visibility. Either a user event fires, or a system event fires, and I know when the DOM manipulation occurs because I'm causing it via javascript. I don't see a case where that javascript DOM manipulation is outside of my code's awareness.
Maybe a scrollbarVisibilityChange event would be convenient, but it's certainly not necessary. This strikes me as a non-issue, 9 years later. Am I missing something?
If you only need to detect the scroll appearance on Windows browsers (except IE), here's my solution with Resize Observer API for vertical scroll as an example.
Idea
Append <div> with position: fixed to <body>
Make it 100% width and observe for size changes
The appearance of the scroll reduces the <div>'s width, which in turn calls the observer callback.
Why only Windows browsers?
Mobile and macOS browsers have a disappearing scroll that is taken out of the document flow and doesn't affect the page layout.
Why should the position be fixed and not absolute?
Element with position: fixed is positioned relative to the initial containing block established by the viewport.
position: absolute may fail if the <body> is also absolutely positioned and has a different width than the viewport.
const innerWidthFiller = document.createElement('div')
innerWidthFiller.style.cssText = 'position: fixed; left: 0; right: 0'
document.body.appendChild(innerWidthFiller)
const detectScroll = () => {
const {clientHeight, scrollHeight} = document.documentElement
window.result.value = scrollHeight > clientHeight
}
const resizeObserver = new ResizeObserver(detectScroll)
resizeObserver.observe(innerWidthFiller)
#test {
border: 1px solid;
white-space: nowrap;
}
output {
font-weight: bold;
}
<button onclick="test.style.fontSize='100vh'">Enlarge the text</button>
<button onclick="test.style.fontSize=''">Reset</button>
Page scroll state: <output id="result"></output>
<hr>
<span id="test">Test element</span>