Reputation: 723
I'm trying to create an element that users can hold their mouse over to slowly scroll the page down while they are dragging an element. This is necessary to support my Drag-&-Drop editor.
I'm detecting if the user hovers over the anchor and is dragging, and if so I start an $interval
where I initiate the slow scroll. If the mouse leave the anchor, or the user stops dragging, the slow scroll should be cancelled, but it's not. Below is my code.
elem.on('dragover', mouseOver);
elem.on('dragleave', mouseLeave);
elem.on('dragend', mouseLeave);
var scroller = null;
function mouseOver(ev) {
ev.preventDefault();
console.log('over');
scroller = $interval(function () {
if (!scrolledToBottom()) {
$(window).scrollTop($(window).scrollTop() + 1);
}
}, 10);
}
function mouseLeave(ev) {
ev.preventDefault();
console.log('left');
$interval.cancel(scroller);
scroller = null;
}
function scrolledToBottom() {
return ($(window).scrollTop() + $(window).height()) ==
$(document).height();
}
Even though the console.log
's fire just fine, the scroller never stops scrolling. I think this is because Angular cannot see inside the event as it's triggered and fails to apply the changes to the scroller. However, Angular has no ngDragOver
, and I can only think of JQuery implementations.
Any suggestions?
Upvotes: 0
Views: 3870
Reputation: 1233
The problem with your code is that you have registered events like
elem.on('dragover', mouseOver)
, elem.on('dragleave', mouseLeave)
and elem.on('dragend', mouseLeave)
and as the doc mentions that During the operations, several event types are fired and some event types might be fired many times (for example the drag and dragover event types).
For example your dragover
event handler looks like:
function mouseOver(ev) {
ev.preventDefault();
console.log('over');
scroller = $interval(function () {
if (!scrolledToBottom()) {
$(window).scrollTop($(window).scrollTop() + 1);
}
}, 10);
}
So, the above event handler will fire multiple times registering $interval
multiple time and will loose reference to the old promise.
Fix that by using the following check:
var scroller = null;
function mouseOver(ev) {
ev.preventDefault();
console.log('over');
if (scroller === null) { // <-- This check will prevent multiple $interval registration.
scroller = $interval(function () {
if (!scrolledToBottom()) {
$(window).scrollTop($(window).scrollTop() + 1);
}
}, 10);
}
}
I would remove the elem.on('dragleave', mouseLeave)
event handler registration call as this event handler if registered will also be called as soon as you start the element drag. And thus cancelling the custom scoll
logic.
Also, I would suggest changing your dragend
event handler to make a check before cancelling the $interval
, like(This is just for code clarity):
function mouseLeave(ev) {
ev.preventDefault();
if (scroller !== null) { // <-- Make a check before cancelling the
$interval.cancel(scroller);
scroller = null;
}
}
Here is an working example to try out:
angular.module('myApp', [])
.directive('drag', function($interval) {
return {
restrict: 'A',
link: function(scope, elem, attrs, ctrl) {
elem.on('dragover', mouseOver);
elem.on('dragend', mouseLeave);
var scroller = null;
function mouseOver(ev) {
ev.preventDefault();
if (scroller === null) {
scroller = $interval(function() {
if (!scrolledToBottom()) {
$(window).scrollTop($(window).scrollTop() + 1);
}
}, 10);
}
}
function mouseLeave(ev) {
ev.preventDefault();
if (scroller !== null) {
$interval.cancel(scroller);
scroller = null;
}
}
function scrolledToBottom() {
return ($(window).scrollTop() + $(window).height()) ==
$(document).height();
}
}
}
});
.container {
padding: 0;
margin: 0;
background: blue;
height: 800px;
}
.container .drag {
height: 300px;
}
.container .drag a {
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js "></script>
<div ng-app="myApp">
<div class="container">
<div class="drag">
<a href="" drag>Drag Me</a>
</div>
</div>
</div>
Upvotes: 2