Basically I need to find all elements on the page that have a scrollbar (vertical or horizontal)
How to tell if an element has a scrollbar and can actually be scrolled? I found this code snippet on jsperf. Is it possible to capture the logic behind the code into and XPath expression? Or are there any other ways to check for scrollbars?
Added:
Just to explain what I'm trying to do: I'm developing VimFx - extension for Firefox. Basically it introduces Vim-style mouseless shortcuts (I know there is Vimperator and Pentadactyl...). One of the features I'd like to implement is to allow the use to select the container that's scrolled with j/k keys. That's why I need to discover all scrollable elements on any given random page.
You can check it with javscript is the overflow of a div is set to "scroll"
document.getElementById(elementId).style.overflow == "scroll";
I would check that on every element tough. If all of your elements are divs' then use this:
var allElements = document.getElementsByTagName("div");
for(index in allElements) {
var element = allElements[index];
if (element.style.overflow == "scroll" || element.style.overflowX == "scroll" || element.style.overflowY == "scroll"){
//do something
}
}
I have now implemented support for discovering scrollable elements in VimFx.
The key to solving this problem is to use the Mozilla-specific events overflow
and underflow.
The concept is:
// The function used for the overflow event:
function(event) {
// `window` is a reference to the current window.
var computedStyle = window.getComputedStyle(event.target)
// `overflow: hidden` can cause overflow, but without a scrollbar, so
// exclude such elements
if (computedStyle && computedStyle.getPropertyValue('overflow') == 'hidden') {
return
}
// `scrollableElements` is a reference to a `WeakMap` object for the
// current window.
scrollableElements.set(event.target)
}
// The function used for the underflow event:
function(event) {
scrollableElements.delete(event.target)
}
// Somewhere else in the code we can now get a suitable `scrollableElements`
// object and check if elements are present in it.
if (scrollableElements.has(someElement)) {
// `someElement` is scrollable!
}
Related
I built a small website and dynamically add the code to html divs. After the data has been set, I add a click event and mouse enter/leave event using the id of the div. It is just plain HTML, javascript and css.
When I open the page on a small screen size, the events attach, but when I resize the screen or open on a large size the events do not attach.
I have also tried using both the id and the class selector. The result is the same. I also tried setting the event listener based on the update of the screen size (media query). The html and css is only set once - on load.
I thought that the problem might be happening because of a timeout? How is it best to troubleshoot this?
Javascript
const elements = document.querySelectorAll('div.myClass');
if (window.matchMedia("(max-width: 800px)").matches) {
/* The viewport is less than, or equal to, 800 pixels wide */
console.log(at 800+"this works, but");
} else {
/* The viewport is greater than 800 pixels wide */
console.log("this is not working");
[...elements].forEach( (el) => {
el.addEventListener('click', masterEventHandler, false);
el.addEventListener('mouseenter', inFunction, false);
el.addEventListener('mouseleave', outFunction, false);
});
}
and this is the the setup code I used for the container (note I left out one or two divs - it is just to illustrate):
function display(id){
var container = document.getElementById("myContainer");
var element = document.createElement("div");
var containedElement = document.createElement("div");
element.id = id;
element.className = "myClass";
containedElement.className = "tohideStuffOnLoad";
element.appendChild(containedElement);
container.appendChild(element);
}
The following is my code after it compiled. This remains the same for the lifetime of the page for the lifetime of the app:
<div id="container">
<div id="id" class="inner">
<div id="amnt" class="amount">$43,762.00</div>
<div class="h-overlay m-display">This is the title
<div class="img"><span>▶</span></div>
</div>
<img src="/animage.jpg" alt="my-image">
<div class="ribbon-new"><img src="/new-ribbon.png" alt="ribbon"></div></div>
</div>
I clicked on inspect element and I watched my html - the m-display property toggles on a screen size 800 and smaller and does nothing on a large screen size.
No errors show except for the fact that my events are not firing.
On asking for extra code, I present to you my Event Listeners (I reviewed everything - it is just standard javascript in a script tag):
var inFunction = function(event){
//console.log(event);
event.target.children[1].classList.remove("m-display");
};
var outFunction = function(event){
//console.log(event);
event.target.children[1].classList.add("m-display");
};
var masterEventHandler = function(e){
console.log("Here I redirect to some other stuff");
};
note the above, although I don't think it changes anything
Ok - so here is what went wrong:
The background property of the parent container was transparent.
The z-index was set above content below;
The container was covering content below it.
This was only set for the large screen...
Thus no click methods were firing for content below it :(
Luckily nothing wrong with my Javascript. Thanks for all the help
The following line:
const elements = document.querySelectorAll('div.myClass');
Snapshots all div.myClass elements. What is possibly happening is that after refresh different elements are added to .myContainer, ones that weren't snapshotted.
You could try refreshing the list above on resize.
I am writing a web app in HTML and JavaScript for use on an iPhone. What I would like to achieve is preventing the app from elastic scrolling (scrolling past the pages extents and bouncing back). However, I need some of the longer elements of my app to be able to be scrolled (the app has a long canvas).
I have tried many answers to this found elsewhere on the internet, however, all of those solutions either used JQuery, disabled scrolling altogether, used Phonegap or just plain didn't work on IOS 7. How can I do this?
There is a way to achieve this without jQuery:
document.body.addEventListener('touchmove', function(event) {
event.preventDefault();
});
But this is not a proper solution. It's better to wrap your content in some div, and use css property on it:
-webkit-overflow-scrolling: touch;
Here is the example
Edit:
This will only prevent overscroll in webview, not in app. So you need to disable this feature in app config.
If you use phonegap:
<preference name="DisallowOverscroll" value="true" />
More description here
If you don't use phonegap, you can use this.
The above solution was insufficient in my case. It prohibits all scrolling. It is possible to build a solution here that prevents elastic scrolling in iOS, but that still allows scrolling on children. I this took the above solution and added a check that bubbles up the DOM to determine which scrollable element "owns" the scroll event, and if it's the root element of the page, I drop it:
function overflowIsHidden(node) {
var style = getComputedStyle(node);
return style.overflow === 'hidden' || style.overflowX === 'hidden' || style.overflowY === 'hidden';
}
function findNearestScrollableParent(firstNode) {
var node = firstNode;
var scrollable = null;
while(!scrollable && node) {
if (node.scrollWidth > node.clientWidth || node.scrollHeight > node.clientHeight) {
if (!overflowIsHidden(node)) {
scrollable = node;
}
}
node = node.parentNode;
}
return scrollable;
}
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('touchmove', function(event) {
var owner = findNearestScrollableParent(event.target);
if (!owner || owner === document.documentElement || owner === document.body) {
event.preventDefault();
}
});
}, false);
At this point, the body is no longer scrollable or elastically scrollable in iOS, but children elements are. You can then add -webkit-overflow-scrolling: touch; to those children so they are elastic but the document wont be. This will actually capture all scroll events even as you scroll to the bottom of the children, so the window's scroll position wont ever change erroneously. Alternatively you may also consider:
['resize', 'orientationchange', 'scroll'].forEach(function(event) {
window.addEventListener(event, function() {
window.scrollTo(0, 0);
});
});
which in addition to the first code block I shared, should really throw an axe at document scrolling in ios altogether.
So when you have scroll content in body & want to disable elastic scroll
use:
let scrollToTop = $(window).scrollTop();
if (scrollToTop < 0) {
// do something here
}
Because elastic scroll will always have negative value
Based on answer by #umidbek this is how it worked for me
document.getElementById('content-sections').
addEventListener('touchmove', function (event) {
event.preventDefault();
return false;
});
I have some javascript that adds an element to the document. Immediately after adding i am trying to obtain the element's clientWidth and clientHeight. However I am seeing all properties related to size to be 0. But at a later point i notice that the size properties do get populated with actual size values (eventually).
How can I know when the size details are available for an added Html element?
I have tried listening to various events, such as onload and onreadystatechange. However these events seem to only apply for the document itself, not for individual elements.
Thanks much.
DOM changes are postponed until the javascript thread quits, therefore this doesn't work:
someNode.addChild(someElem);
w = someElem.clientWidth // 0
You have to start a new thread in order to work with newly added element:
someNode.addChild(someElem);
setTimeout(function() {
w = someElem.clientWidth // should work
}, 0)
Alternatively (and better), try listening to a DOM mutation event:
document.body.addEventListener('DOMNodeInserted', function(e) {
alert('div height=' + e.target.clientHeight)
})
d = document.createElement("div")
d.appendChild(document.createTextNode("hello"))
t = document.body.appendChild(d)
http://jsfiddle.net/rM5cJ/
You could set an initial dictionary (object literal can be used)—
where you set at the very first start of the document , and after each html being rendered you can do :
<div id='aaa'> </div>
<Script>myObject.Add('aaa',1,doSomethingLater) </script>
You can not use the onload event since it's relevant only for : img / body/iframes.
The object can look like this :
myObject =
{
Add:function (a,b,cb){this[a]=b;cb(a,b)}
}
I am trying to create some javascript that when an object is added to the window, a listener listens for any click on the body except for the placed object and removes the object if anywhere on the window except the actual object itself is clicked.
Through numerous unsuccessful attempts, the idea I came up with is to dynamically add an overlay div to the screen called overlay2 (or whatever, it doesnt matter) and then listen for clicks on that div. When I add the overlay to the window and set the zIndex to a higher number than the top element already placed (say 5000) and then set the zIndex of the only object to be placed above the overlay to an even higher number (say 6000), the overlay still appears on top of everything and I cannot select any of the objects in the div I meant to place above it.
var overlayDiv = document.createElement('div');
overlayDiv.setAttribute('id', 'overlay2');
overlayDiv.style.zIndex = '5000';
overlayDiv.style.width = '100%';
overlayDiv.style.height = '100%';
overlayDiv.style.left = '0';
overlayDiv.style.top = '0';
overlayDiv.style.position = 'absolute';
document.body.appendChild(overlayDiv);
$(container).append(template);
template.style.zIndex = '6000';
//Listeners
//Page click listener. Closes the tool when the page is clicked anywhere but inside the parent.
var initialClick = false;
$('body').on('click.editObjectListeners', function(event) {
var target = EventUtility.getTarget(event);
if(initialClick) {
console.log(target.id);
if(target.id == 'overlay2' && target.id != '') {
$(overlayDiv).remove();
finish();
};
}
initialClick = true;
});
I've determined that this has everything to do with the absolute positioning of the overlayDiv. While testing, if I used absolute positioning to place the template and if I append the template object directly to the body like I did the overlayDiv, the zIndex works above the overlayDiv as I originally anticipated. Unfortunately absolutely positioning this element doesn't make much sense for me beyond testing purposes. Is there a way to get around this?
Turns out that z-index can really only be used successfully with absolute placed elements. Therefore the original plan to solve the body click listener will not work. Instead, I decided to use jQuery and listener objects to listen for the click instead. Its a much cleaner solution, I just had to wrap my head around it. You can view my other solution here.
I don't know how much this will help your particular problem, but I just happened to notice that you may have a problem with your use of jQuery's "on()" method.
First off, you are using jQuery version 1.7+ with that, correct?
Unlike "live", I believe that the on() parameters are event, selector, function.
So where you have this:
$('body').on('click.editObjectListeners', function(event) {
var target = EventUtility.getTarget(event);
if(initialClick) {
console.log(target.id);
if(target.id == 'overlay2' && target.id != '') {
$(overlayDiv).remove();
finish();
};
}
initialClick = true;
});
I think you want this:
$('body').on('click.editObjectListeners', [SOME SELECTOR], function(event) {
var target = EventUtility.getTarget(event);
if(initialClick) {
console.log(target.id);
if(target.id == 'overlay2' && target.id != '') {
$(overlayDiv).remove();
finish();
};
}
initialClick = true;
});
Hope that helps in some small way.
Good luck!
Try setting the zIndex before appending it to container.
template.style.zIndex = '6000';
$(container).append(template);
I have a weird problem.
I try to write a GreaseMonkey script to be run in Firefox and Google Chrome.
With Chrome I tried 2 extensions : "TamperMonkey" and "Blank Canvas Script Handler", mainly because my script check regulary for a new version on an external site and this is considered as cross site scripting and not authorized in Chrome.
To show you my problem, I write a simple test case :
// ==UserScript==
// #name test
// #namespace http://fgs.ericc-dream.fr.nf
// #description test gm script
// #include http://gaia.fallengalaxy.eu/
// #author ericc
// #version 0.0.1
// ==/UserScript==
/* We attach an event listener to the body tag and trigger the function
* 'message' each time that an element is inserted in the page */
var el = document.body;
el.addEventListener('DOMNodeInserted', message, false);
var extraFlag = false;
function message(event) {
/* first we capture the id of the new inserted element
* (the one who created the event) */
var objId = event.target.id;
/* add an event listener on the map container */
if (objId == "extra") {
el = document.getElementById('extra');
el.addEventListener('DOMSubtreeModified',readTest,false);
GM_log(el.style.display);
}
}
function readTest() {
el = document.getElementById('extra');
GM_log(extraFlag);
GM_log(el.style.display);
if ((el.style.display != 'none') && (!extraFlag)) {
alert('extra');
extraFlag = true;
} else if ((el.style.display == 'none')) {
extraFlag = false;
}
}
the div element 'extra' is modified by the page. The problem is that Chrome is unable to read the value of el.style.display and thus extraFlag never become 'false' again.
I use this flag to avoid to run the code several time, the site is heavily JavaScript driven
This code work nicely in Firefox !
I tried to search with Google but can't find a correct answers. Seems easy to change the value of display, but it seems that I'm the only one who try to read it !!!
I write this code because "DOMAttrModified" isn't supported in Chrome :-(
Thanks in advance for your help
ericc
I'm having a hard time understanding exactly what your question is, but it looks like Chrome can read .style.display properties just fine. I just threw the following code into an HTML template and loaded it in Chrome 10:
<div id="div1">
</div>
<div id="div2" style="display: block;">
</div>
<div id="div3" style="display: inline;">
</div>
<div id="div4" style="display: none;">
</div>
<script type="text/javascript">
alert(document.getElementById("div1").style.display);
alert(document.getElementById("div2").style.display);
alert(document.getElementById("div3").style.display);
alert(document.getElementById("div4").style.display);
document.getElementById("div1").style.display = "none";
alert(document.getElementById("div1").style.display);
</script>
The code produced 5 'alert' boxes with the following output:
blockinlinenonenone
So it seems Chome reads this property just fine.
Maybe the issue is that the webpage on which you're running your greasemonkey script is behaving differently in Chrome than in Firefox? Could it be that the ID of the element is different, or the element is being removed from the DOM instead of just being hidden? What would happen if you modified your function with some more checks, kinda like this?
function readTest() {
el = document.getElementById('extra');
if(el)
{
GM_log(extraFlag);
GM_log(el.style.display);
if (el.style.display && (el.style.display != 'none') && (!extraFlag)) {
alert('extra');
extraFlag = true;
} else if ((el.style.display == 'none') || !el.style.display) {
extraFlag = false;
}
}
else
{
GM_log(extraFlag);
GM_log("element not present");
extraFlag = false;
}
}
Does that help? If not, is there any other reason you could think of why el.style.display wouldn't evaluate properly in Chrome?
It might help if we knew more about what you're trying to do with your script, and possibly what web page or code you're trying to run this on.
After several hours and a ton of test case, I finally find an acceptable explanation (but not yet a solution !)
Let's explain the scenario :
1°) the user click on an icon on the screen
2°) the div#extra, which is already present in the page, is made visible by removing its display property (.style.display="")
3°) the div#extra is filed by an AJAX function with some elements depending on which icon was clicked by the user (more than 200 elements in certain case)
4°) the user click on an other icon to close the div
5°) all elements from the div#extra are removed
6°) the div#extra is hidden by putting is display property to 'none' (.style.display="none")
At first, on Firefox, I used "DOMAttrModified" event on the div#extra to check when the display property was modified and react accordingly.
Problem, this event is not supported on Chrome !!
So I replace it by "DOMSubtreeModified" (attached to div#extra) which is supported by both browser .... but not exactly in the same way :-(
On Firefox, an event is fired for every modification in the subtree but also when the element itself is modified.
On Chrome, they are a little bit more strict and fired event only for modification in the subtree .... and this is my issue !
In Firefox,first event is fired at point 2 (in the scenario) and last at point 6 allowing my function to read when the div#extra is made hidden
In Chrome, first event is fired at point 3 and last at point 5 ... so when the the div#extra is hidden my function is not called and I can't modify the flag !!!! CQFD
Now, or I will add an event listener to the body of the page to intercept when the display property is modified, but it will generate a lot of call to my function, or the developer of TamperMonkey said yesterday that his extension now support "DOMAttrModified" (on Chrome) ....
Thanks anyway to take the time to understand my question and your proposed solution
ericc