How to determine when an HTML5 element has been viewed? - javascript

I am wondering if there are any HTML5 events associated with whether or not an element has been viewed or "scrolled into view" by the user.
An example could be a longer page with elements at the bottom, which has yet to be scrolled into the users view...
I have seen jQuery solutions to this problem, however I am only interested in figuring out if weather or not this is achievable purely though the use of HTML5 events and JavaScript.
It should be noted that I have already had a look at the "onfocus" event, which (from it's official description) seems to only be applicable if the user selects or "clicks" somewhere on or within the element itself.

In plain JavaScript you can use the event "scroll" along with getBoundingClientRect().bottom <= window.innerHeight to determine if an html element has come into view.
document.addEventListener("scroll", inView);
function inView() {
if (document.getElementById("viewElement").getBoundingClientRect().bottom <= window.innerHeight) {
console.log("in view");
// uncomment below if you only want it to notify once
// document.removeEventListener("scroll", inView);
}
}
The console prints "in view" when the element comes into view.
<div id="viewElement">Hello there!</div>

There are no built-in events that tell you when an entire DOM element has become viewable/visible on the page due to scrolling or window resizing.
The only way to do this is to keep track of resize and scroll events (which can each cause more or less of your page to be visible) and then use the scroll position and window height and DOM element positions to calculate if your entire DOM element is visible.
Some relevant pieces of code you can either consider using or look into how they work (these tend to be jQuery-based because they are harder to share if not based on a common DOM library):
Lazy Load Plugin for jQuery
Element "in view" Event jQuery Plugin
Check if Element is Visible After Scrolling - plain JS

I had to do something similar to this when I built http://f1circle.com.
When the bottom banner becomes visible, I have to show a spotlight to the user asking him to login.
The code that achieves it using angularjs can be viewed at https://github.com/rajegannathan/angularUtilities/blob/master/directives/eagerload.js
Though it is an angularjs directive, the main logic is in plain javascript. Basically I check if the the last feed's bottom edge is visible and then trigger the spotlight.
I can explain more if required.

As already mentioned, there is no "event" but someone already wrote a method to "detect if a DOM Element is Truly Visible" (the title). It doesn't require JQuery. You might want to check for the value on several events like the document load, scroll or window resize.

Related

What is causing this web page to scroll down when it loads?

I have a page with bunch of 3rd party JS scripts. When I load the page, it scrolls down to a specific div.
I already spent 2 hours trying to find out which code is causing the scroll.
Is there a way to find out which script / part of the code is triggering the scroll?
Wow, was this hard to debug. Seems like the debugger has some missing features, like tracking the emitter of an event.
The problem is WooCommerce. Specifically, it appears that WooCommerce is setting autofocus on the billing_last_name input field. The browser is then automatically scrolling the page to bring the field into view.
One would hope that there is a configuration option to turn off autofocus, but it appears WooCommerce does not provide this.
You can try adding this to your theme
function disable_autofocus_firstname($fields) {
$fields['billing']['billing_first_name']['autofocus'] = false;
return $fields;
}
add_filter('woocommerce_checkout_fields', 'disable_autofocus_firstname');
If that doesn't work, you can create a CSS rule to hide the billing name field and then run a delayed JS function to show the billing name field after the page is fully loaded.
How I debugged it
Per the OP's request, and considering the bounty offered, I will describe how I debugged this.
I'm a little embarrassed that I didn't just say to myself "Hmm, the page scrolls up to a form, the cursor is in the first field of the form, I wonder if it has autofocus set." Unfortunately, I'm not mainly a front-end programmer, and autofocus did not come to mind at first.
I started with the idea that it was being scrolled via JavaScript, either an explicit call to a scroll function or by setting scrollTop on something. I put an event breakpoint on the scroll event and tried to determine where the scroll event was being generated. Although I found the scroll event, I did not find the source of it. All I could determine at this stage is that the scroll event was targeting the document, not something inside it.
I used monitorEvents to listen for events on document and found only 3, a click and 2 scrolls, the last of which was caused by a delayed scroll-to-top function inserted by the OP to work around the first scroll. I put an execution breakpoint on setting that timeout (not executing the function) in an attempt to "divide and conquer", that is, to see if the scroll was happening before or after that. I maintained that breakpoint for the rest of my debugging effort.
The weird thing was that generally, the page would not scroll before it hit that breakpoint, but sometimes it did. I thought that was odd, and although I didn't know what to make of it, it had me on the lookout for something unusual.
I tried searching all the JavaScript for "scroll" and "update" (text) to look for more breakpoints to set, and set a bunch at JavaScript that did scrolling, but nothing hit.
I noticed that there was a lot of JavaScript dynamically updating the page, and thought that maybe the scroll was due to an update of some sort.
I tried putting a jQuery event listener that logged all events on document (since the JS was using custom events not logged by monitorEvents, and I had already determined that document was the target of the scroll event) to emit all the events and see if it was some custom update event. There were a bunch of custom events, and I later generated the events in the console to see if the page would scroll in response. Since I could not get the page to scroll that way, I concluded that events were likely a dead end.
I switched tactics. I looked at where the page was scrolling to, and saw it was scrolling the WooCommerce form into place. So, while stopped at the execution breakpoint (described above), I deleted the entire WooCommerce form from the DOM, and verified that the page no longer scrolled. This had me convinced that whatever the problem was, it was caused by WooCommerce.
Unfortunately, my Google Fu failed me, and I did not immediately find the problem through a Google search. Instead I found how WooCommerce scrolls the page on errors to make sure the error messages are visible. This led me back to the JavaScript.
Still, there was a lot of JavaScript, a lot of it dynamically creating the form (localizing it on the fly), and a bunch of German (which I don't speak), and I wasn't finding any JavaScript causing scrolling. I really wanted to narrow down which JS file was causing the scroll.
Chrome allows you to set a breakpoint on "script first statement" (under Event Listener Breakpoints -> Scripts), so I did that. In addition to stopping at the first line in every script file, it stops at the beginning of every <script> tag on the page. I found this script tag near the bottom of the page
<script type="text/javascript">
if(typeof jQuery == 'undefined' || typeof jQuery.fn.on == 'undefined') {
document.write('<script src="https://www.prored3.de/wp-includes/js/jquery/jquery.js"><\/script>');
document.write('<script src="https://www.prored3.de/wp-includes/js/jquery/jquery-migrate.min.js"><\/script>');
}
</script>
The weird thing about this script tag was that the scroll happened immediately after this script tag was processed, but jQuery was already loaded, so the script actually did nothing. I was also able to confirm via the console that both before and after this script tag (which is before and after the scroll), the DOM was not flagged as ready. This means that all the jQuery ready handlers had not run by the time the scroll happened. That eliminates a lot of JavaScript, and got me thinking about why the scroll happened after but not before this tag.
I guessed that internally, the browser saw the document.write calls and determined that the DOM was not complete until after it passed that tag, but as soon as it was past it, the DOM was complete and it could start processing page-level attributes. That, along with the earlier observations, led me to look at the WooCommerce form more closely and discover the autofocus attribute set on the billing_first_name field.
Oddly enough, I was not able to prevent the scrolling by deleting the autofocus attribute. I don't know why, but I'm guessing it has to do with browser internals and the fact that the DOM was not ready. I was, however, able to prevent the scrolling by hiding the the billing_first_name in put element via CSS, which convinced me it was the cause of the scroll.
Adding "autofocus" to my Google search led me to other complaints of similar behavior with WooCommerce, and combining posts led me to the PHP solution I posted.
Updated
As I don't have OP's page for testing, the following method of finding registered event listener actually DO NOT solve the issue OP is addressing.
However, this is the general method when I want to find a specific event, just reserved for someone's reference.
If I understand your meaning correctly, you want a method to tell you where do the specific events occur. Please tell me if this is not doing what you want.
You can try to add a breakpoint on chrome debugger.
F12 -> Sources -> Event Listener Breakpoints (in list with those Breakpoints, Scope, etc) -> Control -> Click the box of scroll.
For Sure it may captures some other scroll event you are not interested, but you can go through it next by next until you find the one you want.
Besides, there may be also event not related to scroll, you may also need to try focus or DOM Mutation -> DOMFocusIn.

Is it possible for a browser scroll event to be triggered by DOM manipulation (other than user actions or explicitly calling eg. $.ScrollTop)?

I am researching a problem where a user triggers a scroll event, we process it, and in the process of doing so are somehow triggering a second scroll event. Our code is similar to this. In real life, 'thead' is buried inside many levels of containers, etc.
$(window).on('scroll', function(){
$('thead').css('position','fixed');
})
The triggering action seems to be fixing the position of an element. This causes document.height to change, which makes sense, but such actions do not normally cause a scroll event to occur (from what I can tell).
I can reproduce this in our app (which is a mountain of jQuery) with very specific combinations of browser height and document height (I can't see a pattern to it, though; I just know values that work).
I can't reproduce it in a simple case, and I've been trying to all day.
I am confident that $.ScrollTop() or equivalent functions are not being called, and that the user is only making a single gesture.
The jQuery event object looks to me like it is a second user initiated event, eg. there is nothing to suggest that event #2 was caused by event #1.
This is happening on Chrome, haven't tried other browsers. Any suggestions appreciated.
When you make any element to be position:fixed/absolute, your document's height is changed because changed element become out-of-normal-flow and does not push next elements down (read CSS position property). It's the same as removing element from your page.
So, if you're at the bottom of the page and one of elements is gone, browser scrolls page up to compensate removed element's height (to leave you at the bottom of the page).

check document if there is mouse moving or touching like screensaver

as the title says, i would like to know if theres any possibility in javascript (jquery) to check if there is any action in the document, and if not. something like a screensaver should pop up!
if someone is on the page and looks here, looks there and after a while he doesnt do anything, the mouse (or touch finger) stands still, i want to say the document after a minute without activity...
function noactivity() { //after 60000ms start something here }
i want that global for the whole document!
thanks ted
It can be done relatively simply in jquery using:
setTimeout();
http://jsfiddle.net/bernie1227/hNkTy/1/
I had this issue a while back while I was working on an iframe resizing issue. What I wanted was to tell the parent page whenever there is a change in height of the document.
What I found was that jQuery does not give such facility directly. The main reason for this is that there are too many activities happening to DOM which are not visible, when you are watching it (bind). You could however watch for a specific property like mouse moving on a document.
$(document).mousemove(function(e){
console.log(e.pageY);
console.log(e.pageX);
});
But then again that does not at all mean that the user is interacting with your page. That merely signifies that the user is on your page and his mouse is moving. The user might also be not moving the mouse and merely using his keyboard to interact with your page. So now you would have to watch for keyboard interaction aswell.
$(document).keyup(function(e){
console.log('active');
});
Using these you could create a countdown function which checks for a flag after a set interval of time. You could set the flag if user makes an activity. And after a set amount of time that function the 'noactivity()' function id the flag has not been set.
Another approach to watching the document activity could be you watching the DOM subtree being modified.
jQuery(document).bind('DOMSubtreeModified', function() {
console.log('active');
});
This works for Chrome/FireFox/IE8+ but not on Opera (any version). The main reason being that the operation is too heavy on your browser's resources. And I would discourage using this approach because listening for DOM mutation events may harm performance and the relevant W3C working groups are trying to find a better way to do this and deprecate DOM mutation events for this reason - it's hard or impossible to make a good and performant implementation.
I am not saying that the other options that I mentioned above are good either. They are also expensive operations if you are watching document and should be avoided. Another issue with those options is that the iframe content is not particularly the part of your document and these options will not work if user is interacting with iframe content.
So the conclusion is that W3C did not yet finalize a cheap way where user can watch changes in document subtree.

How to navigate to an element in a page using javascript without changing page hash

I need to navigate to the element 'divElem1', on click of a button. Is it possible?
If we enter this link in browser, http://myUrl#divElem1, browser will navigate to the page and to the DIV element 'divElem1'. The same behavior has to be obtained through javascript.
The hash change will not work in my application, as there are other events will be fired on hash change.
So, the following will not work.
document.button.onclick = function () {
location.hash = "#divElem1";
};
document.getElementById("divElem1").focus() is also not working since the element is a div
You can use scrollIntoView for that:
document.getElementById("divElem1").scrollIntoView()
This doesn't give fine grain control over where exactly the target element will end up but it WILL be moved into the view-port. Even more, if the element is inside a scrollable container, both the container and the element inside it will be moved into a position so they are visible in the view-port.
If you want "plain" Javascript, use scrollIntoView(). But it really jumps to that position, see this question and answer.
Using jQuery, it can be done jumpy or with some easing, as explained here.
Easing is recommended to provide a better user experience and to let visitors see and understand what is happening.

What's the most efficient way to handle displaying a dialog/modal in JavaScript?

[UPDATE:] here is a link to test (if you don't want to clone the repo) http://jsfiddle.net/integralist/g9EPu/
I've got a lot of dialogs/modals that need to be displayed when mousing over certain links in a web app.
Table of content (tl;dr)
How I used to do handle it
How I've tried it recently
Which is better?
What about mouseenter/leave?
How I used to do handle it
The way I usually do this is to use event delegation.
So I add one event handler to a container and then check for the relevant element to become the target and then display the relevant dialog.
I normally have one dialog which I change the content for and re-position (saves having lots of different HTML mark-up).
If the mouseover event (for the link) gets triggered then I display the dialog.
If the mouseout event (for the link) gets triggered then I hide the dialog.
If I mouseout of the link which triggered the event handler then I normally need to set a timer to delay hiding the dialog (just long enough) so I can then mouseover the dialog which itself clears the timer set by the mouseout of the link.
I then have a mouseout event bound to the dialog so I can then hide the dialog when the user rolls their mouse off the dialog.
There are two problems I've encountered at this stage, the first happens practically all the time and the other is an edge case I noticed recently which prompted me to try and find a better solution...
The dialog has 'x' number of child elements and rolling the mouse over a child element causes the mouseout event for the dialog to be triggered hence I need to put in checks to see if the element has a parent which is the dialog itself and if so then don't try to hide the dialog.
When using this technique on a <table> element I've found that when the mouse moves too quickly the mouseout/over events don't get triggered.
How I've tried it recently
For example code see: https://github.com/Integralist/Mouse-Over-Out-Script (you should be able to just clone the repo and run the index.html file locally to see what's happening)
But to give a brief explanation...
We bind a mousemove event to the document.documentElement element (but you could do it on the document.body if you wanted) and then we store the x/y co-ordinates of the mouse position. We provide public API access to a 'check' method which lets us know if the position of the mouse is over the element we've provided to 'check' (we measure the elements dimensions and add those onto its x/y co-ordinates).
In the above repo we have a calendar which shows a dialog whenever a particular date has an event on. We're storing all <td>'s that have an event and we set-up a timer for each of those <td>'s (this is because we need to keep calling the 'check' method to see if that <td> has the mouse over it).
So potentially there could be 31+ (because we're showing the first few days of the following month) opportunities for a dialog to be shown and so 31+ timers set!
This example repo works now, where as the first version where I was using event delegation wasn't.
Which is better?
I'm worried about performance on the mousemove version because it can potentially use a lot of timers (depending on how many dialogs you need in a single page). In my calendar example above there is up to 31+ timers that could be running!
What about mouseenter/leave?
I know these events exist and if all browsers supported it then I could safely use the first version and not have to check for child elements causing erroneous mouseout/over events to be triggered. But regardless I don't believe this would have fixed the example with the event calendar where moving the mouse too quickly was meaning the mouseout/over events for the <td>'s weren't being triggered by the browser. Either way, I know you can polyfill this as jQuery provides mouseenter/leave events but looking through their code I couldn't get that to work for my script (as I don't use jQuery or any other general purpose library - ps, and I don't wish to, so please do not suggest that as an option).
Many thanks for any help/advice or guidance someone can provide me.
The dialog has 'x' number of child elements and rolling the mouse over a child element causes the mouseout event for the dialog to be triggered hence I need to put in checks to see if the element has a parent which is the dialog itself and if so then dont try to hide the dialog.
To solve this: in your event code, simply use the function "isAncestor" (see below)
/*
* element = the "target" in your mouseout event handler
* other = the node you really want to check if you're over
*/
isAncestor: function(element, other)
{
while ( element && element != other ) element = element.parentNode;
return ( element != null && element != undefined );
}
So in your mouseout code for your element (let's call it "itemElement"), you'd check it like:
//We're really mousing out, close dialog
if ( !isAncestor( mouseOutEvent.target, itemElement ) )
{
...do something ...
}

Categories