How do I detect a HTML5 drag event entering and leaving the window, like Gmail does? - html

I'd like to be able to highlight the drop area as soon as the cursor carrying a file enters the browser window, exactly the way Gmail does it. But I can't make it work, and I feel like I'm just missing something really obvious.
I keep trying to do something like this:
this.body = $('body').get(0)
this.body.addEventListener("dragenter", this.dragenter, true)
this.body.addEventListener("dragleave", this.dragleave, true)`
But that fires the events whenever the cursor moves over and out of elements other than BODY, which makes sense, but absolutely doesn't work. I could place an element on top of everything, covering the entire window and detect on that, but that'd be a horrible way to go about it.
What am I missing?

I solved it with a timeout (not squeaky-clean, but works):
var dropTarget = $('.dropTarget'),
html = $('html'),
showDrag = false,
timeout = -1;
html.bind('dragenter', function () {
dropTarget.addClass('dragging');
showDrag = true;
});
html.bind('dragover', function(){
showDrag = true;
});
html.bind('dragleave', function (e) {
showDrag = false;
clearTimeout( timeout );
timeout = setTimeout( function(){
if( !showDrag ){ dropTarget.removeClass('dragging'); }
}, 200 );
});
My example uses jQuery, but it's not necessary. Here's a summary of what's going on:
Set a flag (showDrag) to true on dragenter and dragover of the html (or body) element.
On dragleave set the flag to false. Then set a brief timeout to check if the flag is still false.
Ideally, keep track of the timeout and clear it before setting the next one.
This way, each dragleave event gives the DOM enough time for a new dragover event to reset the flag. The real, final dragleave that we care about will see that the flag is still false.

Modified version from Rehmat (thx)
I liked this idea and instead of writing a new answer, I am updating it here itself. It can be made more precise by checking window dimensions.
var body = document.querySelector("body");
body.ondragleave = (e) => {
if (
e.clientX >= 0 && e.clientX <= body.clientWidth
&& e.clientY >= 0 && e.clientY <= body.clientHeight
) {} else {
// do something here
}
}
Old Version
Don't know it this works for all cases but in my case it worked very well
$('body').bind("dragleave", function(e) {
if (!e.originalEvent.clientX && !e.originalEvent.clientY) {
//outside body / window
}
});

Adding the events to document seemed to work? Tested with Chrome, Firefox, IE 10.
The first element that gets the event is <html>, which should be ok I think.
var dragCount = 0,
dropzone = document.getElementById('dropzone');
function dragenterDragleave(e) {
e.preventDefault();
dragCount += (e.type === "dragenter" ? 1 : -1);
if (dragCount === 1) {
dropzone.classList.add('drag-highlight');
} else if (dragCount === 0) {
dropzone.classList.remove('drag-highlight');
}
};
document.addEventListener("dragenter", dragenterDragleave);
document.addEventListener("dragleave", dragenterDragleave);

Here's another solution. I wrote it in React, but I'll explain it at the end if you want to rebuild it in plain JS. It's similar to other answers here, but perhaps slightly more refined.
import React from 'react';
import styled from '#emotion/styled';
import BodyEnd from "./BodyEnd";
const DropTarget = styled.div`
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
background-color:rgba(0,0,0,.5);
`;
function addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions) {
document.addEventListener(type, listener, options);
return () => document.removeEventListener(type, listener, options);
}
function setImmediate(callback: (...args: any[]) => void, ...args: any[]) {
let cancelled = false;
Promise.resolve().then(() => cancelled || callback(...args));
return () => {
cancelled = true;
};
}
function noop(){}
function handleDragOver(ev: DragEvent) {
ev.preventDefault();
ev.dataTransfer!.dropEffect = 'copy';
}
export default class FileDrop extends React.Component {
private listeners: Array<() => void> = [];
state = {
dragging: false,
}
componentDidMount(): void {
let count = 0;
let cancelImmediate = noop;
this.listeners = [
addEventListener('dragover',handleDragOver),
addEventListener('dragenter',ev => {
ev.preventDefault();
if(count === 0) {
this.setState({dragging: true})
}
++count;
}),
addEventListener('dragleave',ev => {
ev.preventDefault();
cancelImmediate = setImmediate(() => {
--count;
if(count === 0) {
this.setState({dragging: false})
}
})
}),
addEventListener('drop',ev => {
ev.preventDefault();
cancelImmediate();
if(count > 0) {
count = 0;
this.setState({dragging: false})
}
}),
]
}
componentWillUnmount(): void {
this.listeners.forEach(f => f());
}
render() {
return this.state.dragging ? <BodyEnd><DropTarget/></BodyEnd> : null;
}
}
So, as others have observed, the dragleave event fires before the next dragenter fires, which means our counter will momentarily hit 0 as we drag files (or whatever) around the page. To prevent that, I've used setImmediate to push the event to the bottom of JavaScript's event queue.
setImmediate isn't well supported, so I wrote my own version which I like better anyway. I haven't seen anyone else implement it quite like this. I use Promise.resolve().then to move the callback to the next tick. This is faster than setImmediate(..., 0) and simpler than many of the other hacks I've seen.
Then the other "trick" I do is to clear/cancel the leave event callback when you drop a file just in case we had a callback pending -- this will prevent the counter from going into the negatives and messing everything up.
That's it. Seems to work very well in my initial testing. No delays, no flashing of my drop target.
Can get the file count too with ev.dataTransfer.items.length

#tyler's answer is the best! I have upvoted it. After spending so many hours I got that suggestion working exactly as intended.
$(document).on('dragstart dragenter dragover', function(event) {
// Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/
if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) {
// Needed to allow effectAllowed, dropEffect to take effect
event.stopPropagation();
// Needed to allow effectAllowed, dropEffect to take effect
event.preventDefault();
$('.dropzone').addClass('dropzone-hilight').show(); // Hilight the drop zone
dropZoneVisible= true;
// http://www.html5rocks.com/en/tutorials/dnd/basics/
// http://api.jquery.com/category/events/event-object/
event.originalEvent.dataTransfer.effectAllowed= 'none';
event.originalEvent.dataTransfer.dropEffect= 'none';
// .dropzone .message
if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) {
event.originalEvent.dataTransfer.effectAllowed= 'copyMove';
event.originalEvent.dataTransfer.dropEffect= 'move';
}
}
}).on('drop dragleave dragend', function (event) {
dropZoneVisible= false;
clearTimeout(dropZoneTimer);
dropZoneTimer= setTimeout( function(){
if( !dropZoneVisible ) {
$('.dropzone').hide().removeClass('dropzone-hilight');
}
}, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better
});

Your third argument to addEventListener is true, which makes the listener run during capture phase (see http://www.w3.org/TR/DOM-Level-3-Events/#event-flow for a visualization). This means that it will capture the events intended for its descendants - and for the body that means all elements on the page. In your handlers, you'll have to check if the element they're triggered for is the body itself. I'll give you my very dirty way of doing it. If anyone knows a simpler way that actually compares elements, I'd love to see it.
this.dragenter = function() {
if ($('body').not(this).length != 0) return;
... functional code ...
}
This finds the body and removes this from the set of elements found. If the set isn't empty, this wasn't the body, so we don't like this and return. If this is body, the set will be empty and the code executes.
You can try with a simple if (this == $('body').get(0)), but that will probably fail miserably.

I was having trouble with this myself and came up with a usable solution, though I'm not crazy about having to use an overlay.
Add ondragover, ondragleave and ondrop to window
Add ondragenter, ondragleave and ondrop to an overlay and a target element
If drop occurs on the window or overlay, it is ignored, whereas the target handles the drop as desired. The reason we need an overlay is because ondragleave triggers every time an element is hovered, so the overlay prevents that from happening, while the drop zone is given a higher z-index so that the files can be dropped. I am using some code snippets found in other drag and drop related questions, so I cannot take full credit. Here's the full HTML:
<!DOCTYPE html>
<html>
<head>
<title>Drag and Drop Test</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<style>
#overlay {
display: none;
left: 0;
position: absolute;
top: 0;
z-index: 100;
}
#drop-zone {
background-color: #e0e9f1;
display: none;
font-size: 2em;
padding: 10px 0;
position: relative;
text-align: center;
z-index: 150;
}
#drop-zone.hover {
background-color: #b1c9dd;
}
output {
bottom: 10px;
left: 10px;
position: absolute;
}
</style>
<script>
var windowInitialized = false;
var overlayInitialized = false;
var dropZoneInitialized = false;
function handleFileSelect(e) {
e.preventDefault();
var files = e.dataTransfer.files;
var output = [];
for (var i = 0; i < files.length; i++) {
output.push('<li>',
'<strong>', escape(files[i].name), '</strong> (', files[i].type || 'n/a', ') - ',
files[i].size, ' bytes, last modified: ',
files[i].lastModifiedDate ? files[i].lastModifiedDate.toLocaleDateString() : 'n/a',
'</li>');
}
document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
}
window.onload = function () {
var overlay = document.getElementById('overlay');
var dropZone = document.getElementById('drop-zone');
dropZone.ondragenter = function () {
dropZoneInitialized = true;
dropZone.className = 'hover';
};
dropZone.ondragleave = function () {
dropZoneInitialized = false;
dropZone.className = '';
};
dropZone.ondrop = function (e) {
handleFileSelect(e);
dropZoneInitialized = false;
dropZone.className = '';
};
overlay.style.width = (window.innerWidth || document.body.clientWidth) + 'px';
overlay.style.height = (window.innerHeight || document.body.clientHeight) + 'px';
overlay.ondragenter = function () {
if (overlayInitialized) {
return;
}
overlayInitialized = true;
};
overlay.ondragleave = function () {
if (!dropZoneInitialized) {
dropZone.style.display = 'none';
}
overlayInitialized = false;
};
overlay.ondrop = function (e) {
e.preventDefault();
dropZone.style.display = 'none';
};
window.ondragover = function (e) {
e.preventDefault();
if (windowInitialized) {
return;
}
windowInitialized = true;
overlay.style.display = 'block';
dropZone.style.display = 'block';
};
window.ondragleave = function () {
if (!overlayInitialized && !dropZoneInitialized) {
windowInitialized = false;
overlay.style.display = 'none';
dropZone.style.display = 'none';
}
};
window.ondrop = function (e) {
e.preventDefault();
windowInitialized = false;
overlayInitialized = false;
dropZoneInitialized = false;
overlay.style.display = 'none';
dropZone.style.display = 'none';
};
};
</script>
</head>
<body>
<div id="overlay"></div>
<div id="drop-zone">Drop files here</div>
<output id="list"><output>
</body>
</html>

I see a lot of overengineered solutions out there. You should be able to achieve this by simply listening to dragenter and dragleave as your gut seemingly told you.
The tricky part is that when dragleave fires, it seems to have its toElement and fromElement inverted from what makes sense in everyday life (which kind of makes sense in logical terms since it's the inverted action of dragenter).
Bottom-line when you move the cursor from the listening element to outside that element, toElement will have the listening element and fromElement will have the outer non-listening element. In our case, fromElement will be null when we drag outside the browser.
Solution
window.addEventListener("dragleave", function(e){
if (!e.fromElement){
console.log("Dragging back to OS")
}
})
window.addEventListener("dragenter", function(e){
console.log("Dragging to browser")
})

The ondragenter is fired quite often. You can avoid using a helper variable like draggedFile. If you don't care how often your on ondragenter function is being called, you can remove that helper variable.
Solution:
let draggedFile = false;
window.ondragenter = (e) => {
if(!draggedFile) {
draggedFile = true;
console.log("dragenter");
}
}
window.ondragleave = (e) => {
if (!e.fromElement && draggedFile) {
draggedFile = false;
console.log("dragleave");
}
}

Have you noticed that there is a delay before the dropzone disappears in Gmail? My guess is that they have it disappear on a timer (~500ms) that gets reset by dragover or some such event.
The core of the problem you described is that dragleave is triggered even when you drag into a child element. I'm trying to find a way to detect this, but I don't have an elegantly clean solution yet.

really sorry to post something that is angular & underscore specific, however the way i solved the problem (HTML5 spec, works on chrome) should be easy to observe.
.directive('documentDragAndDropTrigger', function(){
return{
controller: function($scope, $document){
$scope.drag_and_drop = {};
function set_document_drag_state(state){
$scope.$apply(function(){
if(state){
$document.context.body.classList.add("drag-over");
$scope.drag_and_drop.external_dragging = true;
}
else{
$document.context.body.classList.remove("drag-over");
$scope.drag_and_drop.external_dragging = false;
}
});
}
var drag_enters = [];
function reset_drag(){
drag_enters = [];
set_document_drag_state(false);
}
function drag_enters_push(event){
var element = event.target;
drag_enters.push(element);
set_document_drag_state(true);
}
function drag_leaves_push(event){
var element = event.target;
var position_in_drag_enter = _.find(drag_enters, _.partial(_.isEqual, element));
if(!_.isUndefined(position_in_drag_enter)){
drag_enters.splice(position_in_drag_enter,1);
}
if(_.isEmpty(drag_enters)){
set_document_drag_state(false);
}
}
$document.bind("dragenter",function(event){
console.log("enter", "doc","drag", event);
drag_enters_push(event);
});
$document.bind("dragleave",function(event){
console.log("leave", "doc", "drag", event);
drag_leaves_push(event);
console.log(drag_enters.length);
});
$document.bind("drop",function(event){
reset_drag();
console.log("drop","doc", "drag",event);
});
}
};
})
I use a list to represent the elements that have triggered a drag enter event. when a drag leave event happens i find the element in the drag enter list that matches, remove it from the list, and if the resulting list is empty i know that i have dragged outside of the document/window.
I need to reset the list containing dragged over elements after a drop event occurs, or the next time I start dragging something the list will be populated with elements from the last drag and drop action.
I have only tested this on chrome so far. I made this because Firefox and chrome have different API implementations of HTML5 DND. (drag and drop).
really hope this helps some people.

When the file enters and leaves child elements it fires additional dragenter and dragleave so you need to count up and down.
var count = 0
document.addEventListener("dragenter", function() {
if (count === 0) {
setActive()
}
count++
})
document.addEventListener("dragleave", function() {
count--
if (count === 0) {
setInactive()
}
})
document.addEventListener("drop", function() {
if (count > 0) {
setInactive()
}
count = 0
})

I found out from looking at the spec that if the evt.dataTransfer.dropEffect on dragEnd match none then it's a cancelation.
I did already use that event to handle copying without affecting the clipboard. so this was good for me.
When I hit Esc then the drop effect was equal to none
window.ondragend = evt => {
if (evt.dataTransfer.dropEffect === 'none') abort
if (evt.dataTransfer.dropEffect === 'copy') copy // user holds alt on mac
if (evt.dataTransfer.dropEffect === 'move') move
}

on "dropend" event you can check the value of the document.focus() was the magic trick in my case.

Related

Is it possible to scroll only a div, but not the whole page unless you scroll outside that div? [duplicate]

I have a little "floating tool box" - a div with position:fixed; overflow:auto.
Works just fine.
But when scrolling inside that box (with the mouse wheel) and reaching the bottom OR top, the parent element "takes over" the "scroll request" : The document behind the tool box scrolls.
- Which is annoying and not what the user "asked for".
I'm using jQuery and thought I could stop this behaviour with event.stoppropagation():
$("#toolBox").scroll( function(event){ event.stoppropagation() });
It does enter the function, but still, propagation happens anyway (the document scrolls)
- It's surprisingly hard to search for this topic on SO (and Google), so I have to ask:
How to prevent propagation / bubbling of the scroll-event ?
Edit:
Working solution thanks to amustill (and Brandon Aaron for the mousewheel-plugin here:
https://github.com/brandonaaron/jquery-mousewheel/raw/master/jquery.mousewheel.js
$(".ToolPage").bind('mousewheel', function(e, d)
var t = $(this);
if (d > 0 && t.scrollTop() === 0) {
e.preventDefault();
}
else {
if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
e.preventDefault();
}
}
});
I am adding this answer for completeness because the accepted answer by #amustill does not correctly solve the problem in Internet Explorer. Please see the comments in my original post for details. In addition, this solution does not require any plugins - only jQuery.
In essence, the code works by handling the mousewheel event. Each such event contains a wheelDelta equal to the number of px which it is going to move the scrollable area to. If this value is >0, then we are scrolling up. If the wheelDelta is <0 then we are scrolling down.
FireFox: FireFox uses DOMMouseScroll as the event, and populates originalEvent.detail, whose +/- is reversed from what is described above. It generally returns intervals of 3, while other browsers return scrolling in intervals of 120 (at least on my machine). To correct, we simply detect it and multiply by -40 to normalize.
#amustill's answer works by canceling the event if the <div>'s scrollable area is already either at the top or the bottom maximum position. However, Internet Explorer disregards the canceled event in situations where the delta is larger than the remaining scrollable space.
In other words, if you have a 200px tall <div> containing 500px of scrollable content, and the current scrollTop is 400, a mousewheel event which tells the browser to scroll 120px further will result in both the <div> and the <body> scrolling, because 400 + 120 > 500.
So - to solve the problem, we have to do something slightly different, as shown below:
The requisite jQuery code is:
$(document).on('DOMMouseScroll mousewheel', '.Scrollable', function(ev) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.innerHeight(),
delta = (ev.type == 'DOMMouseScroll' ?
ev.originalEvent.detail * -40 :
ev.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
ev.stopPropagation();
ev.preventDefault();
ev.returnValue = false;
return false;
}
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
In essence, this code cancels any scrolling event which would create the unwanted edge condition, then uses jQuery to set the scrollTop of the <div> to either the maximum or minimum value, depending on which direction the mousewheel event was requesting.
Because the event is canceled entirely in either case, it never propagates to the body at all, and therefore solves the issue in IE, as well as all of the other browsers.
I have also put up a working example on jsFiddle.
All the solutions given in this thread don't mention an existing - and native - way to solve this problem without reordering DOM and/or using event preventing tricks. But there's a good reason: this way is proprietary - and available on MS web platform only. Quoting MSDN:
-ms-scroll-chaining property - specifies the scrolling behavior that occurs when a user hits the scroll limit during a manipulation. Property values:
chained - Initial value. The nearest scrollable parent element begins scrolling when the user hits a scroll limit during a manipulation. No bounce effect is shown.
none - A bounce effect is shown when the user hits a scroll limit during a manipulation.
Granted, this property is supported on IE10+/Edge only. Still, here's a telling quote:
To give you a sense of how popular preventing scroll chaining may be,
according to my quick http-archive search "-ms-scroll-chaining: none"
is used in 0.4% of top 300K pages despite being limited in
functionality and only supported on IE/Edge.
And now good news, everyone! Starting from Chrome 63, we finally have a native cure for Blink-based platforms too - and that's both Chrome (obviously) and Android WebView (soon).
Quoting the introducing article:
The overscroll-behavior property is a new CSS feature that controls
the behavior of what happens when you over-scroll a container
(including the page itself). You can use it to cancel scroll chaining,
disable/customize the pull-to-refresh action, disable rubberbanding
effects on iOS (when Safari implements overscroll-behavior), and more.[...]
The property takes three possible values:
auto - Default. Scrolls that originate on the element may propagate to
ancestor elements.
contain - prevents scroll chaining. Scrolls do not
propagate to ancestors but local effects within the node are shown.
For example, the overscroll glow effect on Android or the
rubberbanding effect on iOS which notifies the user when they've hit a
scroll boundary. Note: using overscroll-behavior: contain on the html
element prevents overscroll navigation actions.
none - same as contain but it also prevents overscroll effects within the node itself (e.g. Android overscroll glow or iOS rubberbanding).
[...] The best part is that using overscroll-behavior does not adversely
affect page performance like the hacks mentioned in the intro!
Here's this feature in action. And here's corresponding CSS Module document.
UPDATE: Firefox, since version 59, has joined the club, and MS Edge is expected to implement this feature in version 18. Here's the corresponding caniusage.
UPDATE 2: And now (Oct, 2022) Safari officially joined the club: since 16.0 version, overscroll-behavior is no longer behind the feature flag.
It's possible with the use of Brandon Aaron's Mousewheel plugin.
Here's a demo: http://jsbin.com/jivutakama/edit?html,js,output
$(function() {
var toolbox = $('#toolbox'),
height = toolbox.height(),
scrollHeight = toolbox.get(0).scrollHeight;
toolbox.bind('mousewheel', function(e, d) {
if((this.scrollTop === (scrollHeight - height) && d < 0) || (this.scrollTop === 0 && d > 0)) {
e.preventDefault();
}
});
});
I know it's quite an old question, but since this is one of top results in google... I had to somehow cancel scroll bubbling without jQuery and this code works for me:
function preventDefault(e) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
document.getElementById('a').onmousewheel = function(e) {
document.getElementById('a').scrollTop -= e. wheelDeltaY;
preventDefault(e);
}
EDIT: CodePen example
For AngularJS, I defined the following directive:
module.directive('isolateScrolling', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.bind('DOMMouseScroll', function (e) {
if (e.detail > 0 && this.clientHeight + this.scrollTop == this.scrollHeight) {
this.scrollTop = this.scrollHeight - this.clientHeight;
e.stopPropagation();
e.preventDefault();
return false;
}
else if (e.detail < 0 && this.scrollTop <= 0) {
this.scrollTop = 0;
e.stopPropagation();
e.preventDefault();
return false;
}
});
element.bind('mousewheel', function (e) {
if (e.deltaY > 0 && this.clientHeight + this.scrollTop >= this.scrollHeight) {
this.scrollTop = this.scrollHeight - this.clientHeight;
e.stopPropagation();
e.preventDefault();
return false;
}
else if (e.deltaY < 0 && this.scrollTop <= 0) {
this.scrollTop = 0;
e.stopPropagation();
e.preventDefault();
return false;
}
return true;
});
}
};
});
And then added it to the scrollable element (the dropdown-menu ul):
<div class="dropdown">
<button type="button" class="btn dropdown-toggle">Rename <span class="caret"></span></button>
<ul class="dropdown-menu" isolate-scrolling>
<li ng-repeat="s in savedSettings | objectToArray | orderBy:'name' track by s.name">
<a ng-click="renameSettings(s.name)">{{s.name}}</a>
</li>
</ul>
</div>
Tested on Chrome and Firefox. Chrome's smooth scrolling defeats this hack when a large mousewheel movement is made near (but not at) the top or bottom of the scroll region.
There are tons of questions like this out there, with many answers, but I could not find a satisfactory solution that did not involve events, scripts, plugins, etc. I wanted to keep it straight in HTML and CSS. I finally found a solution that worked, although it involved restructuring the markup to break the event chain.
1. Basic problem
Scrolling input (i.e.: mousewheel) applied to the modal element will spill over into an ancestor element and scroll it in the same direction, if some such element is scrollable:
(All examples are meant to be viewed on desktop resolutions)
https://jsfiddle.net/ybkbg26c/5/
HTML:
<div id="parent">
<div id="modal">
This text is pretty long here. Hope fully, we will get some scroll bars.
</div>
</div>
CSS:
#modal {
position: absolute;
height: 100px;
width: 100px;
top: 20%;
left: 20%;
overflow-y: scroll;
}
#parent {
height: 4000px;
}
2. No parent scroll on modal scroll
The reason why the ancestor ends up scrolling is because the scroll event bubbles and some element on the chain is able to handle it. A way to stop that is to make sure none of the elements on the chain know how to handle the scroll. In terms of our example, we can refactor the tree to move the modal out of the parent element. For obscure reasons, it is not enough to keep the parent and the modal DOM siblings; the parent must be wrapped by another element that establishes a new stacking context. An absolutely positioned wrapper around the parent can do the trick.
The result we get is that as long as the modal receives the scroll event, the event will not bubble to the "parent" element.
It should typically be possible to redesign the DOM tree to support this behavior without affecting what the end user sees.
https://jsfiddle.net/0bqq31Lv/3/
HTML:
<div id="context">
<div id="parent">
</div>
</div>
<div id="modal">
This text is pretty long here. Hope fully, we will get some scroll bars.
</div>
CSS (new only):
#context {
position: absolute;
overflow-y: scroll;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
3. No scroll anywhere except in modal while it is up
The solution above still allows the parent to receive scroll events, as long as they are not intercepted by the modal window (i.e. if triggered by mousewheel while the cursor is not over the modal). This is sometimes undesirable and we may want to forbid all background scrolling while the modal is up. To do that, we need to insert an extra stacking context that spans the whole viewport behind the modal. We can do that by displaying an absolutely positioned overlay, which can be fully transparent if necessary (but not visibility:hidden).
https://jsfiddle.net/0bqq31Lv/2/
HTML:
<div id="context">
<div id="parent">
</div>
</div>
<div id="overlay">
</div>
<div id="modal">
This text is pretty long here. Hope fully, we will get some scroll bars.
</div>
CSS (new on top of #2):
#overlay {
background-color: transparent;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
Here's a plain JavaScript version:
function scroll(e) {
var delta = (e.type === "mousewheel") ? e.wheelDelta : e.detail * -40;
if (delta < 0 && (this.scrollHeight - this.offsetHeight - this.scrollTop) <= 0) {
this.scrollTop = this.scrollHeight;
e.preventDefault();
} else if (delta > 0 && delta > this.scrollTop) {
this.scrollTop = 0;
e.preventDefault();
}
}
document.querySelectorAll(".scroller").addEventListener("mousewheel", scroll);
document.querySelectorAll(".scroller").addEventListener("DOMMouseScroll", scroll);
As variant, to avoid performance issues with scroll or mousewheel handling, you can use code like below:
css:
body.noscroll {
overflow: hidden;
}
.scrollable {
max-height: 200px;
overflow-y: scroll;
border: 1px solid #ccc;
}
html:
<div class="scrollable">
...A bunch of items to make the div scroll...
</div>
...A bunch of text to make the body scroll...
js:
var $document = $(document),
$body = $('body'),
$scrolable = $('.scrollable');
$scrolable.on({
'mouseenter': function () {
// add hack class to prevent workspace scroll when scroll outside
$body.addClass('noscroll');
},
'mouseleave': function () {
// remove hack class to allow scroll
$body.removeClass('noscroll');
}
});
Example of work: http://jsbin.com/damuwinarata/4
Angular JS Directive
I had to wrap an angular directive. The following is a Mashup of the other answers here. tested on Chrome and Internet Explorer 11.
var app = angular.module('myApp');
app.directive("preventParentScroll", function () {
return {
restrict: "A",
scope: false,
link: function (scope, elm, attr) {
elm.bind('mousewheel', onMouseWheel);
function onMouseWheel(e) {
elm[0].scrollTop -= (e.wheelDeltaY || (e.originalEvent && (e.originalEvent.wheelDeltaY || e.originalEvent.wheelDelta)) || e.wheelDelta || 0);
e.stopPropagation();
e.preventDefault();
e.returnValue = false;
}
}
}
});
Usage
<div prevent-parent-scroll>
...
</div>
Hopes this helps the next person that gets here from a Google search.
Using native element scroll properties with the delta value from the mousewheel plugin:
$elem.on('mousewheel', function (e, delta) {
// Restricts mouse scrolling to the scrolling range of this element.
if (
this.scrollTop < 1 && delta > 0 ||
(this.clientHeight + this.scrollTop) === this.scrollHeight && delta < 0
) {
e.preventDefault();
}
});
In case someone is still looking for a solution for this, the following plugin does the job http://mohammadyounes.github.io/jquery-scrollLock/
It fully addresses the issue of locking mouse wheel scroll inside a given container, preventing it from propagating to parent element.
It does not change wheel scrolling speed, user experience will not be affected. and you get the same behavior regardless of the OS mouse wheel vertical scrolling speed (On Windows it can be set to one screen or one line up to 100 lines per notch).
Demo: http://mohammadyounes.github.io/jquery-scrollLock/example/
Source: https://github.com/MohammadYounes/jquery-scrollLock
You can achieve this outcome with CSS, ie
.isolate-scrolling {
overscroll-behavior: contain;
}
This will only scroll the parent container if your mouse leaves the child element to the parent.
amustill's answer as a knockout handler:
ko.bindingHandlers.preventParentScroll = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
$(element).mousewheel(function (e, d) {
var t = $(this);
if (d > 0 && t.scrollTop() === 0) {
e.preventDefault();
}
else {
if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
e.preventDefault();
}
}
});
}
};
the method above is not that natural, after some googling I find a more nice solution , and no need of jQuery. see [1] and demo [2].
var element = document.getElementById('uf-notice-ul');
var isMacWebkit = (navigator.userAgent.indexOf("Macintosh") !== -1 &&
navigator.userAgent.indexOf("WebKit") !== -1);
var isFirefox = (navigator.userAgent.indexOf("firefox") !== -1);
element.onwheel = wheelHandler; // Future browsers
element.onmousewheel = wheelHandler; // Most current browsers
if (isFirefox) {
element.scrollTop = 0;
element.addEventListener("DOMMouseScroll", wheelHandler, false);
}
// prevent from scrolling parrent elements
function wheelHandler(event) {
var e = event || window.event; // Standard or IE event object
// Extract the amount of rotation from the event object, looking
// for properties of a wheel event object, a mousewheel event object
// (in both its 2D and 1D forms), and the Firefox DOMMouseScroll event.
// Scale the deltas so that one "click" toward the screen is 30 pixels.
// If future browsers fire both "wheel" and "mousewheel" for the same
// event, we'll end up double-counting it here. Hopefully, however,
// cancelling the wheel event will prevent generation of mousewheel.
var deltaX = e.deltaX * -30 || // wheel event
e.wheelDeltaX / 4 || // mousewheel
0; // property not defined
var deltaY = e.deltaY * -30 || // wheel event
e.wheelDeltaY / 4 || // mousewheel event in Webkit
(e.wheelDeltaY === undefined && // if there is no 2D property then
e.wheelDelta / 4) || // use the 1D wheel property
e.detail * -10 || // Firefox DOMMouseScroll event
0; // property not defined
// Most browsers generate one event with delta 120 per mousewheel click.
// On Macs, however, the mousewheels seem to be velocity-sensitive and
// the delta values are often larger multiples of 120, at
// least with the Apple Mouse. Use browser-testing to defeat this.
if (isMacWebkit) {
deltaX /= 30;
deltaY /= 30;
}
e.currentTarget.scrollTop -= deltaY;
// If we ever get a mousewheel or wheel event in (a future version of)
// Firefox, then we don't need DOMMouseScroll anymore.
if (isFirefox && e.type !== "DOMMouseScroll") {
element.removeEventListener("DOMMouseScroll", wheelHandler, false);
}
// Don't let this event bubble. Prevent any default action.
// This stops the browser from using the mousewheel event to scroll
// the document. Hopefully calling preventDefault() on a wheel event
// will also prevent the generation of a mousewheel event for the
// same rotation.
if (e.preventDefault) e.preventDefault();
if (e.stopPropagation) e.stopPropagation();
e.cancelBubble = true; // IE events
e.returnValue = false; // IE events
return false;
}
[1] https://dimakuzmich.wordpress.com/2013/07/16/prevent-scrolling-of-parent-element-with-javascript/
[2] http://jsfiddle.net/dima_k/5mPkB/1/
This actually works in AngularJS.
Tested on Chrome and Firefox.
.directive('stopScroll', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.bind('mousewheel', function (e) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.height(),
delta = (e.type == 'DOMMouseScroll' ?
e.originalEvent.detail * -40 :
e.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
e.stopPropagation();
e.preventDefault();
e.returnValue = false;
return false;
};
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
}
};
})
my jQuery plugin:
$('.child').dontScrollParent();
$.fn.dontScrollParent = function()
{
this.bind('mousewheel DOMMouseScroll',function(e)
{
var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
if (delta > 0 && $(this).scrollTop() <= 0)
return false;
if (delta < 0 && $(this).scrollTop() >= this.scrollHeight - $(this).height())
return false;
return true;
});
}
I have a similar situation and here's how i solved it:
All my scrollable elements get the class scrollable.
$(document).on('wheel', '.scrollable', function(evt) {
var offsetTop = this.scrollTop + parseInt(evt.originalEvent.deltaY, 10);
var offsetBottom = this.scrollHeight - this.getBoundingClientRect().height - offsetTop;
if (offsetTop < 0 || offsetBottom < 0) {
evt.preventDefault();
} else {
evt.stopImmediatePropagation();
}
});
stopImmediatePropagation() makes sure not to scroll parent scrollable area from scrollable child area.
Here's a vanilla JS implementation of it:
http://jsbin.com/lugim/2/edit?js,output
New web dev here. This worked like a charm for me on both IE and Chrome.
static preventScrollPropagation(e: HTMLElement) {
e.onmousewheel = (ev) => {
var preventScroll = false;
var isScrollingDown = ev.wheelDelta < 0;
if (isScrollingDown) {
var isAtBottom = e.scrollTop + e.clientHeight == e.scrollHeight;
if (isAtBottom) {
preventScroll = true;
}
} else {
var isAtTop = e.scrollTop == 0;
if (isAtTop) {
preventScroll = true;
}
}
if (preventScroll) {
ev.preventDefault();
}
}
}
Don't let the number of lines fool you, it is quite simple - just a bit verbose for readability (self documenting code ftw right?)
Also I should mention that the language here is TypeScript, but as always, it is straightforward to convert it to JS.
We can simply use CSS.
Give a style to the child scroll container element.
style="overscroll-behavior: contain"
It doesn't trigger the parent's scroll event.
For those using MooTools, here is equivalent code:
'mousewheel': function(event){
var height = this.getSize().y;
height -= 2; // Not sure why I need this bodge
if ((this.scrollTop === (this.scrollHeight - height) && event.wheel < 0) ||
(this.scrollTop === 0 && event.wheel > 0)) {
event.preventDefault();
}
Bear in mind that I, like some others, had to tweak a value by a couple of px, that is what the height -= 2 is for.
Basically the main difference is that in MooTools, the delta info comes from event.wheel instead of an extra parameter passed to the event.
Also, I had problems if I bound this code to anything (event.target.scrollHeight for a bound function does not equal this.scrollHeight for a non-bound one)
Hope this helps someone as much as this post helped me ;)
Check out Leland Kwong's code.
Basic idea is to bind the wheeling event to the child element, and then use the native javascript property scrollHeight and the jquery property outerHeight of the child element to detect the end of the scroll, upon which return false to the wheeling event to prevent any scrolling.
var scrollableDist,curScrollPos,wheelEvent,dY;
$('#child-element').on('wheel', function(e){
scrollableDist = $(this)[0].scrollHeight - $(this).outerHeight();
curScrollPos = $(this).scrollTop();
wheelEvent = e.originalEvent;
dY = wheelEvent.deltaY;
if ((dY>0 && curScrollPos >= scrollableDist) ||
(dY<0 && curScrollPos <= 0)) {
return false;
}
});
I yoinked this from the chosen library: https://github.com/harvesthq/chosen/blob/master/coffee/chosen.jquery.coffee
function preventParentScroll(evt) {
var delta = evt.deltaY || -evt.wheelDelta || (evt && evt.detail)
if (delta) {
evt.preventDefault()
if (evt.type == 'DOMMouseScroll') {
delta = delta * 40
}
fakeTable.scrollTop = delta + fakeTable.scrollTop
}
}
var el = document.getElementById('some-id')
el.addEventListener('mousewheel', preventParentScroll)
el.addEventListener('DOMMouseScroll', preventParentScroll)
This works for me.
jQuery plugin with emulate natural scrolling for Internet Explorer
$.fn.mousewheelStopPropagation = function(options) {
options = $.extend({
// defaults
wheelstop: null // Function
}, options);
// Compatibilities
var isMsIE = ('Microsoft Internet Explorer' === navigator.appName);
var docElt = document.documentElement,
mousewheelEventName = 'mousewheel';
if('onmousewheel' in docElt) {
mousewheelEventName = 'mousewheel';
} else if('onwheel' in docElt) {
mousewheelEventName = 'wheel';
} else if('DOMMouseScroll' in docElt) {
mousewheelEventName = 'DOMMouseScroll';
}
if(!mousewheelEventName) { return this; }
function mousewheelPrevent(event) {
event.preventDefault();
event.stopPropagation();
if('function' === typeof options.wheelstop) {
options.wheelstop(event);
}
}
return this.each(function() {
var _this = this,
$this = $(_this);
$this.on(mousewheelEventName, function(event) {
var origiEvent = event.originalEvent;
var scrollTop = _this.scrollTop,
scrollMax = _this.scrollHeight - $this.outerHeight(),
delta = -origiEvent.wheelDelta;
if(isNaN(delta)) {
delta = origiEvent.deltaY;
}
var scrollUp = delta < 0;
if((scrollUp && scrollTop <= 0) || (!scrollUp && scrollTop >= scrollMax)) {
mousewheelPrevent(event);
} else if(isMsIE) {
// Fix Internet Explorer and emulate natural scrolling
var animOpt = { duration:200, easing:'linear' };
if(scrollUp && -delta > scrollTop) {
$this.stop(true).animate({ scrollTop:0 }, animOpt);
mousewheelPrevent(event);
} else if(!scrollUp && delta > scrollMax - scrollTop) {
$this.stop(true).animate({ scrollTop:scrollMax }, animOpt);
mousewheelPrevent(event);
}
}
});
});
};
https://github.com/basselin/jquery-mousewheel-stop-propagation/blob/master/mousewheelStopPropagation.js
The best solution I could find was listening to the scroll event on the window and set the scrollTop to the previous scrollTop if the child div was visible.
prevScrollPos = 0
$(window).scroll (ev) ->
if $('#mydiv').is(':visible')
document.body.scrollTop = prevScrollPos
else
prevScrollPos = document.body.scrollTop
There is a flicker in the background of the child div if you fire a lot of scroll events, so this could be tweaked, but it is hardly noticed and it was sufficient for my use case.
Don't use overflow: hidden; on body. It automatically scrolls everything to the top. There's no need for JavaScript either. Make use of overflow: auto;:
HTML Structure
<div class="overlay">
<div class="overlay-content"></div>
</div>
<div class="background-content">
lengthy content here
</div>
Styling
.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.8);
.overlay-content {
height: 100%;
overflow: scroll;
}
}
.background-content{
height: 100%;
overflow: auto;
}
Play with the demo here.
There's also a funny trick to lock the parent's scrollTop when mouse hovers over a scrollable element. This way you don't have to implement your own wheel scrolling.
Here's an example for preventing document scroll, but it can be adjusted for any element.
scrollable.mouseenter(function ()
{
var scroll = $(document).scrollTop();
$(document).on('scroll.trap', function ()
{
if ($(document).scrollTop() != scroll) $(document).scrollTop(scroll);
});
});
scrollable.mouseleave(function ()
{
$(document).off('scroll.trap');
});
M.K. offered a great plugin in his answer. Plugin can be found here. However, for the sake of completion, I thought it'd be a good idea to put it together in one answer for AngularJS.
Start by injecting the bower or npm (whichever is preferred)
bower install jquery-scrollLock --save
npm install jquery-scroll-lock --save
Add the following directive. I am choosing to add it as an attribute
(function() {
'use strict';
angular
.module('app')
.directive('isolateScrolling', isolateScrolling);
function isolateScrolling() {
return {
restrict: 'A',
link: function(sc, elem, attrs) {
$('.scroll-container').scrollLock();
}
}
}
})();
And the important piece the plugin fails to document in their website is the HTML structure that it must follow.
<div class="scroll-container locked">
<div class="scrollable" isolate-scrolling>
... whatever ...
</div>
</div>
The attribute isolate-scrolling must contain the scrollable class and it all needs to be inside the scroll-container class or whatever class you choose and the locked class must be cascaded.
It is worth to mention that with modern frameworks like reactJS, AngularJS, VueJS, etc, there are easy solutions for this problem, when dealing with fixed position elements. Examples are side panels or overlaid elements.
The technique is called a "Portal", which means that one of the components used in the app, without the need to actually extract it from where you are using it, will mount its children at the bottom of the body element, outside of the parent you are trying to avoid scrolling.
Note that it will not avoid scrolling the body element itself. You can combine this technique and mounting your app in a scrolling div to achieve the expected result.
Example Portal implementation in React's material-ui: https://material-ui-next.com/api/portal/
There is ES 6 crossbrowser + mobile vanila js decision:
function stopParentScroll(selector) {
let last_touch;
let MouseWheelHandler = (e, selector) => {
let delta;
if(e.deltaY)
delta = e.deltaY;
else if(e.wheelDelta)
delta = e.wheelDelta;
else if(e.changedTouches){
if(!last_touch){
last_touch = e.changedTouches[0].clientY;
}
else{
if(e.changedTouches[0].clientY > last_touch){
delta = -1;
}
else{
delta = 1;
}
}
}
let prevent = function() {
e.stopPropagation();
e.preventDefault();
e.returnValue = false;
return false;
};
if(selector.scrollTop === 0 && delta < 0){
return prevent();
}
else if(selector.scrollTop === (selector.scrollHeight - selector.clientHeight) && delta > 0){
return prevent();
}
};
selector.onwheel = e => {MouseWheelHandler(e, selector)};
selector.onmousewheel = e => {MouseWheelHandler(e, selector)};
selector.ontouchmove = e => {MouseWheelHandler(e, selector)};
}
I was searching for this for MooTools and this was the first that came up.
The original MooTools example would work with scrolling up, but not scrolling down so I decided to write this one.
MooTools 1.4.5: http://jsfiddle.net/3MzFJ/
MooTools 1.3.2: http://jsfiddle.net/VhnD4/
MooTools 1.2.6: http://jsfiddle.net/xWrw4/
var stopScroll = function (e) {
var scrollTo = null;
if (e.event.type === 'mousewheel') {
scrollTo = (e.event.wheelDelta * -1);
} else if (e.event.type === 'DOMMouseScroll') {
scrollTo = 40 * e.event.detail;
}
if (scrollTo) {
e.preventDefault();
this.scrollTo(0, scrollTo + this.scrollTop);
}
return false;
};
Usage:
(function)($){
window.addEvent('domready', function(){
$$('.scrollable').addEvents({
'mousewheel': stopScroll,
'DOMMouseScroll': stopScroll
});
});
})(document.id);

prototype - Trigger event when an element is removed from the DOM

I'm trying to figure out how to execute some js code when an element is removed from the page:
Something in prototype like:
$('custom-div').observe('remove', function(event) {
// Handle the event
});
Does anything like this exist?
In modern browsers, you can use a MutationObserver. Here's code that will call a callback when a DOM element is removed from it's current location:
function watchRemove(el, fn) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var item;
if (mutation.type === "childList" && mutation.removedNodes) {
for (var i = 0; i < mutation.removedNodes.length; i++) {
item = mutation.removedNodes[i];
if (item === el) {
// clear the observer
observer.disconnect();
fn.call(item, item);
break;
}
}
}
});
});
observer.observe(el.parentNode, {childList: true});
return observer;
}
And, a working demo: http://jsfiddle.net/jfriend00/naft3qeb/
This watches the parent for changes to its direct children and calls the callback if the specific DOM element passed in is removed.
The observer will remove itself when the DOM element is removed or watchRemove() returns the observer instance which you can call .disconnect() on at any time to stop the watching.
Here's a jQuery plug-in that uses this functionality:
jQuery.fn.watchRemove = function(fn, observers) {
return this.each(function() {
var o = watchRemove(this, fn);
if (observers) {
observers.push(o);
}
});
}
In this case, it accepts an optional array object as an argument that will be filled with all the observer objects (only necessary to pass this if you want to be able to stop the watching yourself on any given item).

Unable to access subfolder html file through <a> tag

I have a main folder with index.html file for my html app. I have written a code in index.html of main folder to access the file (index.html) present in the sub folder as follows,
SubFile
When i click on the above link, it is not navigating to the subfile and instead the link of main folder index.html file changes to mainfolder/index.html#!/subfolder/index.html
I even tried changing the name of subfolder file but no success. What could be the problem?
I also want to navigate back to the main folder index.html from subfolder as follow,
Mainfile
But it is also not working. How can I achieve this as well?
Edited:
The file my-app.js is creating the issue. The code of my-app.js is as follows,
// Initialize your app
var myApp = new Framework7({
animateNavBackIcon: true,
// Enable templates auto precompilation
precompileTemplates: true,
// Enabled pages rendering using Template7
swipeBackPage: false,
swipeBackPageThreshold: 1,
swipePanel: "left",
swipePanelCloseOpposite: true,
pushState: true,
pushStateRoot: undefined,
pushStateNoAnimation: false,
pushStateSeparator: '#!/',
template7Pages: true
});
// Export selectors engine
var $$ = Dom7;
// Add main View
var mainView = myApp.addView('.view-main', {
// Enable dynamic Navbar
dynamicNavbar: false
});
$$(document).on('pageInit', function (e) {
$(".swipebox").swipebox();
$("#ContactForm").validate({
submitHandler: function(form) {
ajaxContact(form);
return false;
}
});
$('a.backbutton').click(function(){
parent.history.back();
return false;
});
$(".posts li").hide();
size_li = $(".posts li").size();
x=4;
$('.posts li:lt('+x+')').show();
$('#loadMore').click(function () {
x= (x+1 <= size_li) ? x+1 : size_li;
$('.posts li:lt('+x+')').show();
if(x == size_li){
$('#loadMore').hide();
$('#showLess').show();
}
});
$("a.switcher").bind("click", function(e){
e.preventDefault();
var theid = $(this).attr("id");
var theproducts = $("ul#photoslist");
var classNames = $(this).attr('class').split(' ');
if($(this).hasClass("active")) {
// if currently clicked button has the active class
// then we do nothing!
return false;
} else {
// otherwise we are clicking on the inactive button
// and in the process of switching views!
if(theid == "view13") {
$(this).addClass("active");
$("#view11").removeClass("active");
$("#view11").children("img").attr("src","images/switch_11.png");
$("#view12").removeClass("active");
$("#view12").children("img").attr("src","images/switch_12.png");
var theimg = $(this).children("img");
theimg.attr("src","images/switch_13_active.png");
// remove the list class and change to grid
theproducts.removeClass("photo_gallery_11");
theproducts.removeClass("photo_gallery_12");
theproducts.addClass("photo_gallery_13");
}
else if(theid == "view12") {
$(this).addClass("active");
$("#view11").removeClass("active");
$("#view11").children("img").attr("src","images/switch_11.png");
$("#view13").removeClass("active");
$("#view13").children("img").attr("src","images/switch_13.png");
var theimg = $(this).children("img");
theimg.attr("src","images/switch_12_active.png");
// remove the list class and change to grid
theproducts.removeClass("photo_gallery_11");
theproducts.removeClass("photo_gallery_13");
theproducts.addClass("photo_gallery_12");
}
else if(theid == "view11") {
$("#view12").removeClass("active");
$("#view12").children("img").attr("src","images/switch_12.png");
$("#view13").removeClass("active");
$("#view13").children("img").attr("src","images/switch_13.png");
var theimg = $(this).children("img");
theimg.attr("src","images/switch_11_active.png");
// remove the list class and change to grid
theproducts.removeClass("photo_gallery_12");
theproducts.removeClass("photo_gallery_13");
theproducts.addClass("photo_gallery_11");
}
}
});
document.addEventListener('touchmove', function(event) {
if(event.target.parentNode.className.indexOf('navbarpages') != -1 || event.target.className.indexOf('navbarpages') != -1 ) {
event.preventDefault(); }
}, false);
// Add ScrollFix
var scrollingContent = document.getElementById("pages_maincontent");
new ScrollFix(scrollingContent);
var ScrollFix = function(elem) {
// Variables to track inputs
var startY = startTopScroll = deltaY = undefined,
elem = elem || elem.querySelector(elem);
// If there is no element, then do nothing
if(!elem)
return;
// Handle the start of interactions
elem.addEventListener('touchstart', function(event){
startY = event.touches[0].pageY;
startTopScroll = elem.scrollTop;
if(startTopScroll <= 0)
elem.scrollTop = 1;
if(startTopScroll + elem.offsetHeight >= elem.scrollHeight)
elem.scrollTop = elem.scrollHeight - elem.offsetHeight - 1;
}, false);
};
})
What shall i remove from it to solve my problem?
#!/subfolder/index.html
This make me feel that you are using a single page application framework/library, like Angular or something related. So maybe your problem is not in the html but in your javascript code.
Please remove all javascript and check it will work fine then revert all js one by one and test you will find the conflict javascript resolve that conflict. it will work fine.

Is there a generic ScreenSpaceEvent that captures all events?

To react to specific space handlers I typically do this -
var fooHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
fooHandler.setInputAction(function(movement){
// do stuff
}, Cesium.ScreenSpaceEventType.WHEEL);
This function would be limited to WHEEL inputs. I have a couple of things that I need to do every time the camera changes position or height. I tried creating an event handler for the camera in a fashion similar to the above, and then calling camera.positionCartographic within that function, but to no avail.
Is there an event in Cesium that captures any movement?
You don't want to use ScreenSpaceEventHandler to do this. Instead, you subscribe to the preRender event and compare the camera position from last frame. Here's some sample code for you:
var lastTime = Cesium.getTimestamp();
var lastPosition = viewer.scene.camera.position.clone();
function preRender(scene) {
var time = Cesium.getTimestamp();
var position = scene.camera.position;
if (!Cesium.Cartesian3.equalsEpsilon(lastPosition, position, Cesium.Math.EPSILON4)) {
document.getElementById('viewChanged').style.display = 'block';
lastTime = time;
} else if (time - lastTime > 250) {
//hide the 'view changed' message after 250 ms of inactivity
lastTime = time;
document.getElementById('viewChanged').style.display = 'none';
}
lastPosition = position.clone();
}
viewer.scene.preRender.addEventListener(preRender);
We plan on adding a viewChanged event to Cesium some time soon, perhaps with 1.8, but this code will continue to work after that and you'll be able to switch to the event at your leisure.
If you want a live demo of the above code, see this port of the view changed Google Earth demo we did in Cesium: http://analyticalgraphicsinc.github.io/cesium-google-earth-examples/examples/viewchangeEvent.html
Here's what I ended up doing:
_preRender = function (scene) {
var currentPosition = scene.camera.position;
if (!Cesium.Cartesian3.equalsEpsilon(_lastPosition, currentPosition, Cesium.Math.EPSILON4)) {
_lastPosition = currentPosition.clone();
if (typeof _positionChangeTimeout !== 'undefined' && _positionChangeTimeout !== null)
{
clearTimeout(_positionChangeTimeout);
}
var currentPositionCartographic = scene.camera.positionCartographic;
_positionChangeTimeout = setTimeout(function() {
if (typeof _positionChangeListener === 'function' && _positionChangeListener !== null)
{
_positionChangeListener({
lat: Cesium.Math.toDegrees(currentPositionCartographic.latitude),
long: Cesium.Math.toDegrees(currentPositionCartographic.longitude),
zoomLevel: _calcZoomForAltitude(currentPositionCartographic.height, currentPositionCartographic.latitude)
});
}
}, 250);
}
}

html color to alternative rows of dynamic table

I have a Dynamic table that I want to give color to on alternative rows. How can I achieve this with css? I need the code to work in IE7+
Look into using even/odd rules in CSS3.
Reference: https://developer.mozilla.org/en/CSS/:nth-child
For instance,
tr:nth-child(odd) will represent the CSS for every 2n + 1 child, whereas tr:nth-child(even) will represent the CSS for every 2n child.
i came across this same problem Friday, i used the jquery solution of
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
a stack overflow solution .js posted here
Detect changes in the DOM
so essentially you add the .js script in the head and fire the jquery rules on dom change.
My finished .js looked like this
<script type="text/javascript">
(function (window) {
var last = +new Date();
var delay = 100; // default delay
// Manage event queue
var stack = [];
function callback() {
var now = +new Date();
if (now - last > delay) {
for (var i = 0; i < stack.length; i++) {
stack[i]();
}
last = now;
}
}
// Public interface
var onDomChange = function (fn, newdelay) {
if (newdelay)
delay = newdelay;
stack.push(fn);
};
// Naive approach for compatibility
function naive() {
var last = document.getElementsByTagName('*');
var lastlen = last.length;
var timer = setTimeout(function check() {
// get current state of the document
var current = document.getElementsByTagName('*');
var len = current.length;
// if the length is different
// it's fairly obvious
if (len != lastlen) {
// just make sure the loop finishes early
last = [];
}
// go check every element in order
for (var i = 0; i < len; i++) {
if (current[i] !== last[i]) {
callback();
last = current;
lastlen = len;
break;
}
}
// over, and over, and over again
setTimeout(check, delay);
}, delay);
}
//
// Check for mutation events support
//
var support = {};
var el = document.documentElement;
var remain = 3;
// callback for the tests
function decide() {
if (support.DOMNodeInserted) {
window.addEventListener("DOMContentLoaded", function () {
if (support.DOMSubtreeModified) { // for FF 3+, Chrome
el.addEventListener('DOMSubtreeModified', callback, false);
} else { // for FF 2, Safari, Opera 9.6+
el.addEventListener('DOMNodeInserted', callback, false);
el.addEventListener('DOMNodeRemoved', callback, false);
}
}, false);
} else if (document.onpropertychange) { // for IE 5.5+
document.onpropertychange = callback;
} else { // fallback
naive();
}
}
// checks a particular event
function test(event) {
el.addEventListener(event, function fn() {
support[event] = true;
el.removeEventListener(event, fn, false);
if (--remain === 0) decide();
}, false);
}
// attach test events
if (window.addEventListener) {
test('DOMSubtreeModified');
test('DOMNodeInserted');
test('DOMNodeRemoved');
} else {
decide();
}
// do the dummy test
var dummy = document.createElement("div");
el.appendChild(dummy);
el.removeChild(dummy);
// expose
window.onDomChange = onDomChange;
})(window);
$(document).ready(function () {
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
onDomChange(function () {
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
});
});
</script>
I would like to caveat this answer that this probably is not the greatest solution but worked for what i needed it to do. :-)
CSS3 nth-child selector:
tr:nth-child(odd) {
background: red /* or whatever */;
}
You can use a CSS3 selector:
tr:nth-child(even) {background: #CCC}
tr:nth-child(odd) {background: #FFF}
or jQuery:
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
or do it on the server side.