Javascript scrollbar class and mousewheel speed in different browsers - javascript

I'm getting many reports that the mousewheel behaves differently in different browsers when using this scrollbar class. In some browsers (like Firefox) it's extremely slow while in others (mostly newer versions of Safari on Snow Leopard) it's perfect.
Any ideas what's going on here and how to fix it? I'm using the Mootools library. One line to pay attention to here is the wheel: (Browser.firefox) ? 20 : 1 line. This is where you set the speed or steps for the mousewheel.
Here it is set up in a jsFiddle: http://jsfiddle.net/brandondurham/6SUyM/
var ScrollBar = new Class({
Implements: [Events, Options],
options: {
wheel: (Browser.firefox) ? 20 : 1
},
initialize: function(main, options) {
this.setOptions(options);
this.main = $(main);
this.content = this.main.getFirst();
this.vScrollbar = new Element('div', {
'class': 'scrollbar'
}).inject(this.content, 'after');
this.vTrack = new Element('div', {
'class': 'track'
}).inject(this.vScrollbar);
this.vThumb = new Element('div', {
'class': 'handle'
}).inject(this.vTrack);
this.bound = {
'vStart': this.vStart.bind(this),
'end': this.end.bind(this),
'vDrag': this.vDrag.bind(this),
'vTouchDrag': this.vTouchDrag.bind(this),
'wheel': this.wheel.bind(this),
'vPage': this.vPage.bind(this),
};
this.vScrollbar.set('tween', {
duration: 200,
transition: 'cubic:out'
});
this.main.addEvent('mouseenter', function(event){
this.vScrollbar.get('tween').cancel();
this.vScrollbar.tween('width', 12);
}.bind(this));
this.main.addEvent('mouseleave', function(event){
this.vScrollbar.get('tween').cancel();
this.vScrollbar.tween('width', 0);
}.bind(this));
this.vPosition = {};
this.vMouse = {};
this.update();
this.attach();
this.scrollContent = new Fx.Scroll(this.content, {
duration: 800,
transition: Fx.Transitions.Cubic.easeOut,
});
this.scrollThumb = new Fx.Morph(this.vThumb, {
duration: 400,
transition: Fx.Transitions.Cubic.easeOut,
});
},
update: function() {
var panel_id = (this.content.getFirst()) ? this.content.getFirst().get('id') : '';
if ((this.content.scrollHeight <= this.main.offsetHeight) || panel_id == 'random-doodle') this.main.addClass('noscroll');
else this.main.removeClass('noscroll');
this.vContentSize = this.content.offsetHeight;
this.vContentScrollSize = this.content.scrollHeight;
this.vTrackSize = this.vTrack.offsetHeight;
this.vContentRatio = this.vContentSize / this.vContentScrollSize;
this.vThumbSize = (this.vTrackSize * this.vContentRatio).limit(12, this.vTrackSize);
this.vScrollRatio = this.vContentScrollSize / this.vTrackSize;
this.vThumb.setStyle('height', this.vThumbSize);
this.vUpdateThumbFromContentScroll();
this.vUpdateContentFromThumbPosition();
},
vUpdateContentFromThumbPosition: function() {
this.content.scrollTop = this.vPosition.now * this.vScrollRatio;
},
vUpdateContentFromThumbPosition2: function() {
var pos = this.vPosition.now * this.vScrollRatio;
this.scrollContent.start(0, pos);
},
vUpdateThumbFromContentScroll: function() {
this.vPosition.now = (this.content.scrollTop / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));
this.vThumb.setStyle('top', this.vPosition.now);
},
vUpdateThumbFromContentScroll2: function(pos) {
this.vPosition.now = (this.content.scrollTopNew / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));
this.scrollThumb.start({
'top': this.vPosition.now
});
},
attach: function() {
if (this.options.wheel) this.content.addEvent('mousewheel', this.bound.wheel);
this.content.addEvent('touchstart', this.bound.vStart);
this.vThumb.addEvent('mousedown', this.bound.vStart);
this.vTrack.addEvent('mouseup', this.bound.vPage);
},
wheel: function(event) {
this.content.scrollTop -= event.wheel * this.options.wheel;
this.vUpdateThumbFromContentScroll();
event.stop();
},
scrollTo: function(pos){
myInstance = this;
this.content.scrollTopNew = pos;
this.scrollContent.start(0, this.content.scrollTopNew);
myInstance.vUpdateThumbFromContentScroll2(pos);
},
vPage: function(event) {
// if scrolling up
if (event.page.y > this.vThumb.getPosition().y) {
myInstance = this;
this.content.scrollTopNew = this.content.scrollTop.toInt() + this.content.offsetHeight.toInt();
this.scrollContent.start(0, this.content.scrollTopNew);
}
// if scrolling down
else {
myInstance = this;
this.content.scrollTopNew = this.content.scrollTop.toInt() - this.content.offsetHeight.toInt();
this.scrollContent.start(0, this.content.scrollTopNew);
}
myInstance.vUpdateThumbFromContentScroll2(event.page.y);
event.stop();
},
vStart: function(event) {
this.vMouse.start = event.page.y;
this.vPosition.start = this.vThumb.getStyle('top').toInt();
document.addEvent('touchmove', this.bound.vTouchDrag);
document.addEvent('touchend', this.bound.end);
document.addEvent('mousemove', this.bound.vDrag);
document.addEvent('mouseup', this.bound.end);
this.vThumb.addEvent('mouseup', this.bound.end);
event.stop();
},
end: function(event) {
document.removeEvent('touchmove', this.bound.vTouchDrag);
document.removeEvent('mousemove', this.bound.vDrag);
document.removeEvent('mouseup', this.bound.end);
this.vThumb.removeEvent('mouseup', this.bound.end);
event.stop();
},
vTouchDrag: function(event) {
this.vMouse.now = event.page.y;
this.vPosition.now = (this.vPosition.start - (this.vMouse.now - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));
this.vUpdateContentFromThumbPosition();
this.vUpdateThumbFromContentScroll();
event.stop();
},
vDrag: function(event) {
this.vMouse.now = event.page.y;
this.vPosition.now = (this.vPosition.start + (this.vMouse.now - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));
this.vUpdateContentFromThumbPosition();
this.vUpdateThumbFromContentScroll();
event.stop();
}
});

The mouse wheel event is very dodgy in javascript, main issue being usually Safari as they used to adjust the ratio on every point release, and even then the values reported by the event are not the same in all major browsers.
There has been some discussion about this on the MooTools tracker some time ago (link) and comparing different solutions I concluded that there's no standard way to normalize the event.
The last message on that issue shows a possible solution for normalizing (link), but it breaks wheel acceleration in Safari (and probably any other acceleration that other Browser/OS/Mouse Driver combination offers) so it is a tradeoff that you will have to evaluate if it fits to the requirements of your usage scenario.

Related

Highcharts: Tooltip delay before display

On my highchart i need a delay before the series tooltip is displayed.
I defined a new refresh function with a timer to realize it. If the timer is ready i check if the mouse position. If it moved not that much the tooltip should appear.
function (H) {
var timer = [];
var mousePosition = {
x: 0,
y: 0
};
window.addEventListener("mousemove", function (event) {
mousePosition.x = event.pageX;
mousePosition.y = event.pageY;
});
var getMousePositionX = function () {
return mousePosition.x;
};
var clearTimer = function () {
timer = [];
}
H.wrap(H.Tooltip.prototype, 'refresh', function (proceed) {
var mousePosX = getMousePositionX();
var delayForDisplay = this.chart.options.tooltip.delayForDisplay ? this.chart.options.tooltip.delayForDisplay : 1000;
timer[timer.length+1] = window.setTimeout(function () {
var currMousePosX = getMousePositionX();
if ((mousePosX >= currMousePosX - 5 && mousePosX <= currMousePosX + 5)) {
this.proceed.apply(this.tooltip, this.refreshArguments);
clearTimer();
}
}.bind({
refreshArguments: Array.prototype.slice.call(arguments, 1),
chart: this.chart,
tooltip: this,
clearTimer: clearTimer,
proceed: proceed
}), delayForDisplay);
});
};
The problem I have is, that the hover holos have also a delay.
Here is a sample: JSFiddle
Any solutions for this issue?
You can make new tooltip basing on your standard Highcharts tooltip and show it on your mouseover with some timeout:
load: function() {
chart = this;
this.myTooltip = new Highcharts.Tooltip(this, this.options.tooltip);
this.tooltip.label.element.remove();
}
point: {
events: {
mouseOver: function(e) {
var i = this.x;
points = [];
Highcharts.each(this.series.chart.series, function(s) {
Highcharts.each(s.data, function(p) {
if (p.x === i) {
points.push(p)
}
})
});
myTooltip = chart.myTooltip;
setTimeout(function() {
myTooltip.refresh(points, e)
}, 1000)
}, mouseOut: function() {
setTimeout(function() {
chart.myTooltip.hide();
}, 1000)
}
}
}
Here you can see an example how it can work: http://jsfiddle.net/az39das8/

Scroll to bottom using ngScrollbar

I am trying to automatically scroll to bottom whenever there is a new message.
I use angular directive ng-scrollbar.
Here is the code of the directive there is one method of scroll named scrolTo:
/**
* #name ng-scrollbar
* #author angrytoro
* #since 9/12/2014
* #version 0.1
* #beta 0.2
* #see https://github.com/angrytoro/ngscrollbar
* #copyright 2014 angrytoro
* #license MIT: You are free to use and modify this code, on the condition that this copyright notice remains.
*
* #description The angular directive ng-scrollbar imitate the true browser scrollbar.
* It's applied to the element which set height or width attribute and the overflow is auto, but exclude body element.
* It's not necessary to imitate scrollbar for body element, if you use the AngularJS.
* suggests: don't use the directive, if you don't have to. The scrollbar which is inbuilt in browser is more highly-efficient.
*AngularJS is not fit for IE which version is less then 9, so the directive is not fit the IE(8,7,6,5).
*
*
* #example
* 1.
* <div style="height:300px;overflow:auto;" ng-scrollbar>
* <li ng-repeat="item in items">item</li>
* </div>
* 2.
* <div style="height:300px;overflow:auto;" ng-scrollbar scrollbar-x="false" scrollbar-y="true" scrollbar-config="{show:true, autoResize: true, dragSpeed: 1.2}">
* <li ng-repeat="item in items">item</li>
* </div>
* 3.
* <div ng-scrollbar>
* <div style="height:400px;width:3000px"></div>
* </div>
*
* #conf spec
* scrollbar-x the value is true or false, to configure the x scrollbar create or no create, the default value is true. but the directive can decide whether it need be created if user not set the attribute.
*
* scrollbar-y the value is true or false, to configure the y scrollbar create or no create, the default value is true. but the directive can decide whether it need be created if user not set the attribute.
*
* scrollbar-config
* default config is
*
* {
* dragSpeed: 1, //default browser delta value is 120 or -120
autoResize: false, // if need auto resize, default false
show: false, // if need show when mouse not enter the container element which need scrollbar, default false.
scrollbar: {
width: 6, //scrollbar width
hoverWidth: 8, //scrollbar width when the mouse hover on it
color: 'rgba(0,0,0,.6)' //scrollbar background color
},
scrollbarContainer: {
width: 12, //scrollbarContainer width
color: 'rgba(0,0,0,.1)' // scrollbarContainer background
}
* }
*
*/
angular.module('widget.scrollbar', [])
.directive('ngScrollbar', [
function() {
return {
restrict: 'AE',
transclude: true,
scope: {
scrollbarConfig: '=scrollbarConfig',
scrollbarX: '#', // the value is true or false, to configure the x scrollbar create or no create.
scrollbarY: '#' // the value is true or false, to configure the y scrollbar create or no create.
},
template: '<div style="position:relative;width:100%;height:100%;">\
<div class="ngscroll-content-container" style="display:inline-block;margin-top:0;margin-left:0" ng-transclude>\
</div>\
<ng-scrollbar-x ng-if="scrollbarX || scrollbarX === undefined"></ng-scrollbar-x>\
<ng-scrollbar-y ng-if="scrollbarY || scrollbarY === undefined"></ng-scrollbar-y>\
</div>',
controller: 'scrollbarController',
compile: function(element) {
element.css('overflow', 'hidden');
return function(scope, element, attrs, ctrl) {
ctrl.init(element, scope.scrollbarConfig);
};
}
};
}
])
.controller('scrollbarController', [function() {
var defaultConfig = {
dragSpeed: 1, //default browser delta value is 120 or -120
autoResize: false, // if need auto resize, default false
show: false, // if need show when mouse not enter the container element which need scrollbar, default false.
scrollbar: {
width: 6, //scrollbar width
hoverWidth: 8, //scrollbar width when the mouse hover on it
color: 'rgba(0,0,0,.6)' //scrollbar background color
},
scrollbarContainer: {
width: 12, //scrollbarContainer width
color: 'rgba(0,0,0,.1)' // scrollbarContainer background
}
};
var containerElement, // the element which need the directive of ngscrollbar
contentElement, // the element which transclude the true content
config, // config
scrollbarMargin, // the variable is used to descide the scrollbar element top or left to its parent element scrollbarContainer
scrollbarHoverMargin; // the variable is used to descide the scrollbar element top or left to its parent element scrollbarContainer when the mouse hover on the scrollbar
/**
* it must be called before the controller is used.
* #param {jqlite object} element it's necessary variable
* #param {object} scrollbarConfig the config which is defined by user
* #return
*/
this.init = function(element, scrollbarConfig) {
containerElement = element;
config = angular.copy(angular.extend(defaultConfig, scrollbarConfig || {}));
contentElement = angular.element(element[0].querySelector('.ngscroll-content-container'));
scrollbarMargin = (config.scrollbarContainer.width - config.scrollbar.width) / 2;
scrollbarHoverMargin = (config.scrollbarContainer.width - config.scrollbar.hoverWidth) / 2;
};
angular.extend(this, {
/**
* Wrap window in an angular jqLite object.
*/
winEl: angular.element(window),
/**
* get the element which need the directive of ngscrollbar
* #return {jqlite object}
*/
getContainerElement: function() {
return containerElement;
},
/**
* the element which transclude the true content
* #return {jqlite object}
*/
getContentElement: function() {
return contentElement;
},
/**
* get the config
* #return {object}
*/
getConfig: function() {
return config;
},
/**
* get the scrollbarMargin
* #return {number}
*/
getScrollbarMargin: function() {
return scrollbarMargin;
},
/**
* get the scrollbarHoverMargin
* #return {number}
*/
getScrollbarHoverMargin: function() {
return scrollbarHoverMargin;
}
});
}])
.directive('ngScrollbarY', ['$timeout', function($timeout){
return {
restrict: 'AE',
require: '^ngScrollbar',
replace: true,
template: '<div class="ngscrollbar-container-y" ng-style="styles.scrollbarContainer"><div class="ngscrollbar-y" ng-style="styles.scrollbar"></div></div>',
compile: function() {
return function(scope, element, attrs, ctrl) {
var config = ctrl.getConfig(),
docEl = angular.element(document),
containerElement = ctrl.getContainerElement(),
contentElement = ctrl.getContentElement(),
scrollbar = angular.element(element[0].querySelector('.ngscrollbar-y')),
scrollbarMargin = ctrl.getScrollbarMargin(),
scrollbarHoverMargin = ctrl.getScrollbarHoverMargin();
scope.styles = {
scrollbarContainer: {
position: 'absolute',
width: config.scrollbarContainer.width + 'px',
height: '100%',
top: 0,
right: 0,
transition: 'background .3s ease-in-out',
'border-radius': config.scrollbarContainer.width / 2 + 'px'
},
scrollbar: {
position: 'absolute',
width: config.scrollbar.width + 'px',
right: scrollbarMargin + 'px',
cursor: 'default',
opacity: 0,
transition: 'opacity .3s ease-in-out, border-radius .1s linear, width .1s linear, right .1s linear',
background: config.scrollbar.color,
'border-radius': config.scrollbar.width / 2 + 'px'
}
};
var getContentHeight = function() {
return contentElement[0].offsetHeight;
};
var getContainerHeight = function() {
return containerElement[0].offsetHeight;
};
var getScrollbarHeight = function() {
var height = Math.pow(getContainerHeight(), 2) / getContentHeight() - scrollbarMargin*2;
return height;
};
var isOverflow = function() {
return getContentHeight() > getContainerHeight();
};
var hideScrollbar = function() {
scrollbar.css('opacity', 0);
};
var showScrollbar = function() {
scrollbar.css('opacity', 1);
};
var reset = function() {
var oldMarginTop = parseInt(contentElement.css('margin-top'), 10);
contentElement.css('margin-top', '0px'); // this is for the element which has the attribute of max-height
if (isOverflow()) {
element.css('display', 'block');
scrollbar.css('height', getScrollbarHeight() + 'px');
scrollTo(oldMarginTop);
if (config.show) {
showScrollbar();
}
} else {
element.css('display', 'none');
}
};
var scrollTo = function(top) {
top = Math.min(0, Math.max(top, getContainerHeight() - getContentHeight()));
contentElement.css('margin-top', top + 'px');
scrollbar.css('top', -top/getContentHeight()*getContainerHeight() + scrollbarMargin + 'px');
};
var scroll = function(distance) {
var newTop = parseInt(contentElement.css('margin-top'), 10) + distance;
scrollTo(newTop);
};
containerElement.on('mousewheel', function(event) {
if (!isOverflow()) {
return;
}
event.preventDefault();
if (event.originalEvent !== undefined) {
event = event.originalEvent;
}
scroll(event.wheelDeltaY || event.wheelDelta);
});
if(window.navigator.userAgent.toLowerCase().indexOf('firefox') >= 0) {
containerElement.on('wheel', function(event) {
if (!isOverflow()) {
return;
}
event.preventDefault();
if (event.originalEvent !== undefined) {
event = event.originalEvent;
}
scroll(-event.deltaY * 40);// the ff delta value is 3 or -3 when scroll and the chrome or ie is -120 or 120, so it must multiply by 40
});
}
element.on('mouseenter', function() {
element.css('background', config.scrollbarContainer.color);
scrollbar.css('width', config.scrollbar.hoverWidth + 'px');
scrollbar.css('right', scrollbarHoverMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.hoverWidth / 2 + 'px');
});
element.on('mouseleave', function() {
element.css('background', 'none');
scrollbar.css('width', config.scrollbar.width + 'px');
scrollbar.css('right', scrollbarMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.width / 2 + 'px');
});
var scrollbarMousedown = false,
axisY,
mouseInElement = false;
if (!config.show) {
containerElement.on('mouseenter', function() {
mouseInElement = true;
showScrollbar();
});
containerElement.on('mouseleave', function() {
mouseInElement = false;
if (scrollbarMousedown) {
return;
}
hideScrollbar();
});
}
scrollbar.on('mousedown', function(event) {
event.preventDefault();
axisY = event.screenY;
scrollbarMousedown = true;
docEl.one('mouseup', function() {
scrollbarMousedown = false;
if (!config.show && !mouseInElement) {
hideScrollbar();
}
// docEl.off('mouseup', arguments.callee);
});
});
docEl.on('mousemove', function(event) {
if(scrollbarMousedown) {
event.preventDefault();
scroll(-(event.screenY - axisY) * config.dragSpeed * getContentHeight() / getContainerHeight());
axisY = event.screenY;
}
});
$timeout(function() {
reset();
if (!!document.createStyleSheet) { //if the browser is ie browser
contentElement.on('DOMNodeInserted', reset);
contentElement.on('DOMNodeRemoved', reset);
} else {
var observer = new MutationObserver(function(mutations){
if (mutations.length) {
reset();
}
});
observer.observe(contentElement[0], {childList:true, subtree: true});
}
}, 5);
// Redraw the scrollbar when window size changes.
if (config.autoResize) {
// Closure to guard against leaking variables.
(function () {
var redrawTimer;
ctrl.winEl.on('resize', function (e) {
if (redrawTimer) {
clearTimeout(redrawTimer);
}
redrawTimer = setTimeout(function () {
redrawTimer = null;
reset();
}, 50);
});
})();
}
};
}
};
}])
.directive('ngScrollbarX', ['$timeout', function($timeout) {
return {
restrict: 'AE',
replace: true,
require: '^ngScrollbar',
template: '<div class="ngscrollbar-container-x" ng-style="styles.scrollbarContainer"><div class="ngscrollbar-x" ng-style="styles.scrollbar"></div></div>',
compile: function() {
return function(scope, element, attrs, ctrl) {
var config = ctrl.getConfig(),
docEl = angular.element(document),
containerElement = ctrl.getContainerElement(),
containerDom = containerElement[0],
contentElement = ctrl.getContentElement(), //the container of content
scrollbar = angular.element(element[0].querySelector('.ngscrollbar-x')),
scrollbarMargin = ctrl.getScrollbarMargin(),
scrollbarHoverMargin = ctrl.getScrollbarHoverMargin();
scope.styles = {
scrollbarContainer: {
position: 'absolute',
width: '100%',
transition: 'background .3s ease-in-out',
'border-radius': config.scrollbarContainer.width / 2 + 'px'
},
scrollbar: {
position: 'absolute',
cursor: 'default',
opacity: 0,
transition: 'opacity .3s ease-in-out, border-radius .1s linear, width .1s linear, right .1s linear',
background: config.scrollbar.color,
'border-radius': config.scrollbar.width / 2 + 'px'
}
};
element.css('height', config.scrollbarContainer.width + 'px'); // set the scrollbarContainer height;
element.css('bottom', 0); // set scrollbarContainer top
element.css('left', 0); //set scrollbarContainer left
scrollbar.css('top', scrollbarMargin + 'px'); //set scrollbar top
scrollbar.css('height', config.scrollbar.width + 'px');
var getContentWidth = function() {
return contentElement[0].offsetWidth;
};
var getContainerWidth = function() {
return containerDom.offsetWidth;
};
var getScrollbarWidth = function() {
return Math.pow(getContainerWidth(), 2) / getContentWidth() - scrollbarMargin * 2;
};
var showScrollbar = function() {
scrollbar.css('opacity', 1);
};
var hideScrollbar = function() {
scrollbar.css('opacity', 0);
};
var isOverflow = function() {
return getContentWidth() > getContainerWidth();
};
var reset = function() {
var oldMarginLeft = parseInt(contentElement.css('margin-left'), 10);
contentElement.css('margin-left', '0px');
if (isOverflow()) {
element.css('display', 'block');
scrollbar.css('width', getScrollbarWidth() + 'px');
scrollTo(oldMarginLeft);
if (config.show) {
showScrollbar();
}
} else {
element.css('display', 'none');
}
};
var scrollTo = function(left) {
left = Math.min(0, Math.max(left, getContainerWidth() - getContentWidth()));
contentElement.css('margin-left', left + 'px');
scrollbar.css('left', -left/getContentWidth()*getContainerWidth() + scrollbarMargin + 'px');
};
var scroll = function(distance) {
var left = parseInt(contentElement.css('margin-left'), 10) + distance;
scrollTo(left);
};
element.on('mouseenter', function() {
element.css('background', config.scrollbarContainer.color);
scrollbar.css('height', config.scrollbar.hoverWidth + 'px');
scrollbar.css('top', scrollbarHoverMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.hoverWidth / 2 + 'px');
});
element.on('mouseleave', function() {
element.css('background', 'none');
scrollbar.css('height', config.scrollbar.width + 'px');
scrollbar.css('top', scrollbarMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.width / 2 + 'px');
});
var scrollbarMousedown = false,
axisX,
mouseInElement = false;
if (!config.show) {
containerElement.on('mouseenter', function() {
mouseInElement = true;
showScrollbar();
});
containerElement.on('mouseleave', function() {
mouseInElement = false;
if (scrollbarMousedown) {
return;
}
hideScrollbar();
});
}
scrollbar.on('mousedown', function(event) {
event.preventDefault();
scrollbarMousedown = true;
axisX = event.screenX;
docEl.one('mouseup', function() {
scrollbarMousedown = false;
if (!config.show && !mouseInElement) {
hideScrollbar();
}
// docEl.off('mouseup', arguments.callee);
});
});
docEl.on('mousemove', function(event) {
if(scrollbarMousedown) {
event.preventDefault();
scroll(-(event.screenX - axisX) * config.dragSpeed * getContentWidth() / getContainerWidth());
axisX = event.screenX;
}
});
$timeout(function() {
reset();
if (!!document.createStyleSheet) { //if the browser is ie browser
contentElement.on('DOMNodeInserted', reset);
contentElement.on('DOMNodeRemoved', reset);
} else {
var observer = new MutationObserver(function(mutations){
if (mutations.length) {
reset();
}
});
observer.observe(contentElement[0], {childList:true, subtree: true});
}
}, 5);
// Redraw the scrollbar when window size changes.
if (config.autoResize) {
// Closure to guard against leaking variables.
(function () {
var redrawTimer;
ctrl.winEl.on('resize', function (e) {
if (redrawTimer) {
clearTimeout(redrawTimer);
}
redrawTimer = setTimeout(function () {
redrawTimer = null;
reset();
}, 50);
});
})();
}
};
}
};
}]);
Thanks for any help to resolve this problem.
You could just use vanilla javascript:
window.scrollTo(0,document.body.scrollHeight);
Run the code above on the event of a new message.
Edit: added example
angular.module('app', [])
.controller('TestCtrl', function($scope) {
$scope.scrollToBottom = function() {
window.scrollTo(0, document.body.scrollHeight);
};
});
#some-content {
height: 10000px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="TestCtrl">
top
<button ng-click="scrollToBottom()">Scroll to bottom</button>
<div id="some-content"></div>
bottom
</div>
I found a very extravagant kosher solution.
I changed code ngScrollbar like this:
angular.module('widget.scrollbar', [])
.directive('ngScrollbar', [
function() {
return {
restrict: 'AE',
transclude: true,
scope: {
name: '#',
scrollbarConfig: '=scrollbarConfig',
scrollbarX: '#', // the value is true or false, to configure the x scrollbar create or no create.
scrollbarY: '#' // the value is true or false, to configure the y scrollbar create or no create.
},
template: '<div style="position:relative;width:100%;height:100%;">\
<div class="ngscroll-content-container" style="display:inline-block;margin-top:0;margin-left:0" ng-transclude>\
</div>\
<ng-scrollbar-x ng-if="scrollbarX || scrollbarX === undefined"></ng-scrollbar-x>\
<ng-scrollbar-y ng-if="scrollbarY || scrollbarY === undefined"></ng-scrollbar-y>\
</div>',
controller: 'scrollbarController',
compile: function(element) {
element.css('overflow', 'hidden');
return function(scope, element, attrs, ctrl) {
ctrl.init(element, scope.scrollbarConfig);
};
}
};
}
])
.controller('scrollbarController', [function() {
var defaultConfig = {
dragSpeed: 1, //default browser delta value is 120 or -120
autoResize: false, // if need auto resize, default false
show: false, // if need show when mouse not enter the container element which need scrollbar, default false.
scrollbar: {
width: 6, //scrollbar width
hoverWidth: 8, //scrollbar width when the mouse hover on it
color: 'rgba(0,0,0,.6)' //scrollbar background color
},
scrollbarContainer: {
width: 12, //scrollbarContainer width
color: 'rgba(0,0,0,.1)' // scrollbarContainer background
}
};
var containerElement, // the element which need the directive of ngscrollbar
contentElement, // the element which transclude the true content
config, // config
scrollbarMargin, // the variable is used to descide the scrollbar element top or left to its parent element scrollbarContainer
scrollbarHoverMargin; // the variable is used to descide the scrollbar element top or left to its parent element scrollbarContainer when the mouse hover on the scrollbar
/**
* it must be called before the controller is used.
* #param {jqlite object} element it's necessary variable
* #param {object} scrollbarConfig the config which is defined by user
* #return
*/
this.init = function(element, scrollbarConfig) {
containerElement = element;
config = angular.copy(angular.extend(defaultConfig, scrollbarConfig || {}));
contentElement = angular.element(element[0].querySelector('.ngscroll-content-container'));
scrollbarMargin = (config.scrollbarContainer.width - config.scrollbar.width) / 2;
scrollbarHoverMargin = (config.scrollbarContainer.width - config.scrollbar.hoverWidth) / 2;
};
angular.extend(this, {
/**
* get the element which need the directive of ngscrollbar
* #return {jqlite object}
*/
getContainerElement: function() {
return containerElement;
},
/**
* the element which transclude the true content
* #return {jqlite object}
*/
getContentElement: function() {
return contentElement;
},
/**
* get the config
* #return {object}
*/
getConfig: function() {
return config;
},
/**
* get the scrollbarMargin
* #return {number}
*/
getScrollbarMargin: function() {
return scrollbarMargin;
},
/**
* get the scrollbarHoverMargin
* #return {number}
*/
getScrollbarHoverMargin: function() {
return scrollbarHoverMargin;
}
});
}])
.directive('ngScrollbarY', ['$timeout', 'ScrollbarDelegate', function($timeout, ScrollbarDelegate){
return {
restrict: 'AE',
require: '^ngScrollbar',
replace: true,
template: '<div class="ngscrollbar-container-y" ng-style="styles.scrollbarContainer"><div class="ngscrollbar-y" ng-style="styles.scrollbar"></div></div>',
compile: function() {
return function(scope, element, attrs, ctrl) {
var config = ctrl.getConfig(),
docEl = angular.element(document),
containerElement = ctrl.getContainerElement(),
contentElement = ctrl.getContentElement(),
scrollbar = angular.element(element[0].querySelector('.ngscrollbar-y')),
scrollbarMargin = ctrl.getScrollbarMargin(),
scrollbarHoverMargin = ctrl.getScrollbarHoverMargin();
scope.styles = {
scrollbarContainer: {
position: 'absolute',
width: config.scrollbarContainer.width + 'px',
height: '100%',
top: 0,
right: 0,
transition: 'background .3s ease-in-out',
'border-radius': config.scrollbarContainer.width / 2 + 'px'
},
scrollbar: {
position: 'absolute',
width: config.scrollbar.width + 'px',
right: scrollbarMargin + 'px',
cursor: 'default',
opacity: 0,
transition: 'opacity .3s ease-in-out, border-radius .1s linear, width .1s linear, right .1s linear',
background: config.scrollbar.color,
'border-radius': config.scrollbar.width / 2 + 'px'
}
};
var getContentHeight = function() {
return contentElement[0].offsetHeight;
};
var getContainerHeight = function() {
return containerElement[0].offsetHeight;
};
var getScrollbarHeight = function() {
var height = Math.pow(getContainerHeight(), 2) / getContentHeight() - scrollbarMargin*2;
return height;
};
var isOverflow = function() {
return getContentHeight() > getContainerHeight();
};
var hideScrollbar = function() {
scrollbar.css('opacity', 0);
};
var showScrollbar = function() {
scrollbar.css('opacity', 1);
};
var reset = function() {
var oldMarginTop = parseInt(contentElement.css('margin-top'), 10);
contentElement.css('margin-top', '0px'); // this is for the element which has the attribute of max-height
if (isOverflow()) {
element.css('display', 'block');
scrollbar.css('height', getScrollbarHeight() + 'px');
scrollTo(oldMarginTop);
if (config.show) {
showScrollbar();
}
} else {
element.css('display', 'none');
}
};
var scrollTo = function(top) {
top = Math.min(0, Math.max(top, getContainerHeight() - getContentHeight()));
contentElement.css('margin-top', top + 'px');
scrollbar.css('top', -top/getContentHeight()*getContainerHeight() + scrollbarMargin + 'px');
};
var scroll = function(distance) {
var newTop = parseInt(contentElement.css('margin-top'), 10) + distance;
scrollTo(newTop);
};
containerElement.on('mousewheel', function(event) {
if (!isOverflow()) {
return;
}
event.preventDefault();
if (event.originalEvent !== undefined) {
event = event.originalEvent;
}
scroll(event.wheelDeltaY || event.wheelDelta);
});
if(window.navigator.userAgent.toLowerCase().indexOf('firefox') >= 0) {
containerElement.on('wheel', function(event) {
if (!isOverflow()) {
return;
}
event.preventDefault();
if (event.originalEvent !== undefined) {
event = event.originalEvent;
}
scroll(-event.deltaY * 40);// the ff delta value is 3 or -3 when scroll and the chrome or ie is -120 or 120, so it must multiply by 40
});
}
element.on('mouseenter', function() {
element.css('background', config.scrollbarContainer.color);
scrollbar.css('width', config.scrollbar.hoverWidth + 'px');
scrollbar.css('right', scrollbarHoverMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.hoverWidth / 2 + 'px');
});
element.on('mouseleave', function() {
element.css('background', 'none');
scrollbar.css('width', config.scrollbar.width + 'px');
scrollbar.css('right', scrollbarMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.width / 2 + 'px');
});
var scrollbarMousedown = false,
axisY,
mouseInElement = false;
if (!config.show) {
containerElement.on('mouseenter', function() {
mouseInElement = true;
showScrollbar();
});
containerElement.on('mouseleave', function() {
mouseInElement = false;
if (scrollbarMousedown) {
return;
}
hideScrollbar();
});
}
scrollbar.on('mousedown', function(event) {
event.preventDefault();
axisY = event.screenY;
scrollbarMousedown = true;
docEl.one('mouseup', function() {
scrollbarMousedown = false;
if (!config.show && !mouseInElement) {
hideScrollbar();
}
// docEl.off('mouseup', arguments.callee);
});
});
docEl.on('mousemove', function(event) {
if(scrollbarMousedown) {
event.preventDefault();
scroll(-(event.screenY - axisY) * config.dragSpeed * getContentHeight() / getContainerHeight());
axisY = event.screenY;
}
});
$timeout(function() {
reset();
if (!!document.createStyleSheet) { //if the browser is ie browser
contentElement.on('DOMNodeInserted', reset);
contentElement.on('DOMNodeRemoved', reset);
} else {
var observer = new MutationObserver(function(mutations){
if (mutations.length) {
reset();
}
});
observer.observe(contentElement[0], {childList:true, subtree: true});
}
}, 5);
var scrollToBottom = function() {
var offset = getContainerHeight() - getContentHeight();
scrollTo(offset);
};
ctrl.scrollTo = scrollTo;
ctrl.scrollToBottom = scrollToBottom;
ctrl.getContentHeight = getContentHeight;
ctrl.getContainerHeight = getContainerHeight;
ctrl.getScrollbarHeight = getScrollbarHeight;
ScrollbarDelegate.registerInstance(scope.name, ctrl);
};
}
};
}])
.directive('ngScrollbarX', ['$timeout', function($timeout) {
return {
restrict: 'AE',
replace: true,
require: '^ngScrollbar',
template: '<div class="ngscrollbar-container-x" ng-style="styles.scrollbarContainer"><div class="ngscrollbar-x" ng-style="styles.scrollbar"></div></div>',
compile: function() {
return function(scope, element, attrs, ctrl) {
var config = ctrl.getConfig(),
docEl = angular.element(document),
containerElement = ctrl.getContainerElement(),
containerDom = containerElement[0],
contentElement = ctrl.getContentElement(), //the container of content
scrollbar = angular.element(element[0].querySelector('.ngscrollbar-x')),
scrollbarMargin = ctrl.getScrollbarMargin(),
scrollbarHoverMargin = ctrl.getScrollbarHoverMargin();
scope.styles = {
scrollbarContainer: {
position: 'absolute',
width: '100%',
transition: 'background .3s ease-in-out',
'border-radius': config.scrollbarContainer.width / 2 + 'px'
},
scrollbar: {
position: 'absolute',
cursor: 'default',
opacity: 0,
transition: 'opacity .3s ease-in-out, border-radius .1s linear, width .1s linear, right .1s linear',
background: config.scrollbar.color,
'border-radius': config.scrollbar.width / 2 + 'px'
}
};
element.css('height', config.scrollbarContainer.width + 'px'); // set the scrollbarContainer height;
element.css('bottom', 0); // set scrollbarContainer top
element.css('left', 0); //set scrollbarContainer left
scrollbar.css('top', scrollbarMargin + 'px'); //set scrollbar top
scrollbar.css('height', config.scrollbar.width + 'px');
var getContentWidth = function() {
return contentElement[0].offsetWidth;
};
var getContainerWidth = function() {
return containerDom.offsetWidth;
};
var getScrollbarWidth = function() {
return Math.pow(getContainerWidth(), 2) / getContentWidth() - scrollbarMargin * 2;
};
var showScrollbar = function() {
scrollbar.css('opacity', 1);
};
var hideScrollbar = function() {
scrollbar.css('opacity', 0);
};
var isOverflow = function() {
return getContentWidth() > getContainerWidth();
};
var reset = function() {
var oldMarginLeft = parseInt(contentElement.css('margin-left'), 10);
contentElement.css('margin-left', '0px');
if (isOverflow()) {
element.css('display', 'block');
scrollbar.css('width', getScrollbarWidth() + 'px');
scrollTo(oldMarginLeft);
if (config.show) {
showScrollbar();
}
} else {
element.css('display', 'none');
}
};
var scrollTo = function(left) {
left = Math.min(0, Math.max(left, getContainerWidth() - getContentWidth()));
contentElement.css('margin-left', left + 'px');
scrollbar.css('left', -left/getContentWidth()*getContainerWidth() + scrollbarMargin + 'px');
};
var scroll = function(distance) {
var left = parseInt(contentElement.css('margin-left'), 10) + distance;
scrollTo(left);
};
element.on('mouseenter', function() {
element.css('background', config.scrollbarContainer.color);
scrollbar.css('height', config.scrollbar.hoverWidth + 'px');
scrollbar.css('top', scrollbarHoverMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.hoverWidth / 2 + 'px');
});
element.on('mouseleave', function() {
element.css('background', 'none');
scrollbar.css('height', config.scrollbar.width + 'px');
scrollbar.css('top', scrollbarMargin + 'px');
scrollbar.css('border-radius', config.scrollbar.width / 2 + 'px');
});
var scrollbarMousedown = false,
axisX,
mouseInElement = false;
if (!config.show) {
containerElement.on('mouseenter', function() {
mouseInElement = true;
showScrollbar();
});
containerElement.on('mouseleave', function() {
mouseInElement = false;
if (scrollbarMousedown) {
return;
}
hideScrollbar();
});
}
scrollbar.on('mousedown', function(event) {
event.preventDefault();
scrollbarMousedown = true;
axisX = event.screenX;
docEl.one('mouseup', function() {
scrollbarMousedown = false;
if (!config.show && !mouseInElement) {
hideScrollbar();
}
// docEl.off('mouseup', arguments.callee);
});
});
docEl.on('mousemove', function(event) {
if(scrollbarMousedown) {
event.preventDefault();
scroll(-(event.screenX - axisX) * config.dragSpeed * getContentWidth() / getContainerWidth());
axisX = event.screenX;
}
});
$timeout(function() {
reset();
if (!!document.createStyleSheet) { //if the browser is ie browser
contentElement.on('DOMNodeInserted', reset);
contentElement.on('DOMNodeRemoved', reset);
} else {
var observer = new MutationObserver(function(mutations){
if (mutations.length) {
reset();
}
});
observer.observe(contentElement[0], {childList:true, subtree: true});
}
}, 5);
};
}
};
}]);
I added this code:
name: '#',
var scrollToBottom = function() {
var offset = getContainerHeight() - getContentHeight();
scrollTo(offset);
};
ctrl.scrollTo = scrollTo;
ctrl.scrollToBottom = scrollToBottom;
ctrl.getContentHeight = getContentHeight;
ctrl.getContainerHeight = getContainerHeight;
ctrl.getScrollbarHeight = getScrollbarHeight;
ScrollbarDelegateService.registerInstance(scope.name, ctrl);
ScrollbarDelegateService - is an angular service through which controls all the scrollbars.
let ScrollbarDelegateService = function () {
let instances = {};
let getInstances = () => {
return instances;
};
let registerInstance = (name, ctrl) => {
instances[name || ''] = ctrl;
};
let deregisterInstance = (name) => {
delete instances[name || ''];
};
let instanceByName = (name) => {
let instance;
if (!(instance = instances[name || ''])) {
return undefined;
}
return instance;
};
return { getInstances, registerInstance, deregisterInstance, instanceByName };
};
export default ScrollbarDelegateService;
And last, to control the scrolling:
In template:
<section ng-scrollbar name="orders" scrollbar-x="false" scrollbar-y="true" scrollbar-config="vm.scrollbarConfig" class="tab-content-inner">
In controller:
let scrollbar = ScrollbarDelegateService.instanceByName('orders');
if (scrollbar !== undefined) {
scrollbar.scrollToBottom();
}

Issue with scoping, when calling a function in a callback

when clicking inside the canvas it will generate a ball and move to the clicked location
when the ball get's to its location I want it to remove itself. But i think i have a problem
with the scope when calling the removeBall() function.
You can find a working example her: jsfiddle
/*
* Main app logic
*/
function Main() {
this.canvas = "canvas";
this.stage = null;
this.WIDTH = 0;
this.HEIGHT = 0;
this.init();
}
Main.prototype.init = function() {
console.clear();
this.stage = new createjs.Stage(this.canvas);
this.resize();
//start game loop
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick", this.gameLoop);
//click event handler
this.stage.on("stagemousedown", function(evt) {
main.fireBall(evt);
});
};
Main.prototype.fireBall = function(evt) {
var bal = new Bal(evt.stageX, evt.stageY);
};
Main.prototype.resize = function() {
//resize the canvas to take max width
this.WIDTH = window.innerWidth;
this.HEIGHT = Math.floor(window.innerWidth * 9 / 16);
this.stage.canvas.width = this.WIDTH;
this.stage.canvas.height = this.HEIGHT;
};
Main.prototype.gameLoop = function() {
//game loop
main.stage.update();
};
/*
* Ball logic
*/
function Bal(toX, toY) {
this.toX = toX ;
this.toY = toY;
this.widthPerc = 8;
this.init();
}
Bal.prototype.width = function() {
return Math.floor(main.stage.canvas.width / 100 * this.widthPerc);
};
Bal.prototype.init = function() {
//create a new ball
this.ball = new createjs.Shape();
this.ball.graphics.beginFill("green").drawCircle(0, 0, this.width());
this.ball.x = (main.stage.canvas.width / 2) - (this.width() / 2);
this.ball.y = main.stage.canvas.height - 20;
main.stage.addChild(this.ball);
this.move();
};
Bal.prototype.move = function() {
//create a tween to cliked coordinates
createjs.Tween.get(this.ball).to({
x: this.toX ,
y: this.toY ,
scaleX:0.4,scaleY:0.4,
rotation: 180
},
750, //speed
createjs.Ease.none
).call(this.removeBall); // <---- How can i pass the correct scope to the called function?
};
Bal.prototype.removeBall = function() {
//try to remove the ball
main.stage.removeChild(this.ball);
};
var main = new Main();
The solution above using bind works, however there is a much better solution. Bind is not available in all browsers (most notably Safari 5.1, which is a modern browser). http://kangax.github.io/es5-compat-table/#Function.prototype.bind
TweenJS has built-in support for scoping functions when using call(). Just pass the scope as the 3rd argument.
Ball.prototype.move = function() {
console.log(this.toX +","+this.toY);
createjs.Tween.get(this.ball).to({
x: this.toX ,
y: this.toY ,
scaleX:0.4,scaleY:0.4,
rotation: 180
},
750, //speed
createjs.Ease.none
).call(this.removeBall, null, this);
};
You can also pass an array of function arguments as the second parameter.
Tween.call(this.removeBall, [this.ball], this);
Ok found a solution! using the bind() functionality.
Bal.prototype.move = function() {
console.log(this.toX +","+this.toY);
createjs.Tween.get(this.ball).to({
x: this.toX ,
y: this.toY ,
scaleX:0.4,scaleY:0.4,
rotation: 180
},
750, //speed
createjs.Ease.none
).call(this.removeBall.bind(this));
};

Kinetic.js masked image with slow performance on firefox

I'm new to kinetic and I don't know about performance issues.
I made this example, you just have to click on the black and white image and drag over it, then, a colored circle apears.
The performance in chrome, safari on an Ipad, and even Opera Mobile on an android phone is quite good. In firefox it starts ok, but if you move the mouse for a while it slows down and doesn't work properly. The firebug profiler doesn't help a lot... How could I debug this issue in a better way?
In the drawing function there's an inner method onMove to do the hard work. I believe here lies the performance problem but I don't know how to achieve the same effect in a better way.
Any ideas?
function draw(images) {
var stage = new Kinetic.Stage({
container : 'container',
width : 1024,
height : 483
}), bn_layer = new Kinetic.Layer(), color_layer = new Kinetic.Layer(), circle_layer = new Kinetic.Layer(), bn = new Kinetic.Image({
x : 0,
y : 0,
image : images.bn,
width : 1024,
heigth : 483
}), tmp_circle = null, movable = false;
bn_layer.add(bn);
tmp_circle = addCircle(circle_layer, images);
var onMove = function() {
if (movable) {
var pos = getMousePosition(stage);
circle_layer.draw();
tmp_circle.remove();
tmp_circle.setPosition(pos.x, pos.y)
tmp_circle.setFillPatternImage(images.color);
tmp_circle.setFillPatternOffset(pos.x, pos.y);
circle_layer.add(tmp_circle);
}
}
stage.on("mousemove touchmove", onMove);
stage.on("mousedown touchstart", function() {
debug("activo")
circle_layer.show();
movable = true;
onMove();
circle_layer.draw();
});
stage.on("mouseup touchend", function() {
debug("inactivo")
circle_layer.draw();
tmp_circle.remove();
circle_layer.hide();
movable = false;
})
//stage.add(color_layer);
stage.add(bn_layer);
stage.add(circle_layer);
circle_layer.hide();
}
Update: Changing the mouse event for a requestAnimationFrame method controlled with a flag the performance improves a lot in firefox on windows. In firefox on Linux the performance is still crappy.
I think this might have some relation with what is commented in this topic:
Poor Canvas2D performance with Firefox on Linux
There they are talking about a possible bug in firefox related to the cairo libraries:
http://blog.mozilla.org/joe/2011/04/26/introducing-the-azure-project/
https://bugzilla.mozilla.org/show_bug.cgi?id=781731
Updated code
function Anim(layer, funcion){
var run = false;
var callback = funcion;
this.layer = layer;
function animate(){
callback();
if (!run){
return;
}
requestAnimFrame(function(){
animate();
})
};
this.start = function(){
run = true;
animate();
};
this.stop = function(){
run = false;
};
}
//draw on frames
function drawAnim(images){
var stage = new Kinetic.Stage({
container : 'container',
width : 1024,
height : 483
}), bn_layer = new Kinetic.Layer(),
hitareas_layer = new Kinetic.Layer(),
circle_layer = new Kinetic.Layer(),
bn = createImage(images.bn),
tmp_circle = null,
movable = false,
hit_areas = null,
c = 0,
colorArea = function() {
if (movable) {
var pos = getMousePosition(stage);
debug("posicion: "+pos.x+" "+pos.y+" " +c+ " " +tmp_circle.getX()+ " "+tmp_circle.getY());
if(pos.x !== tmp_circle.getX() || pos.y !== tmp_circle.getY()){
c++;
circle_layer.draw();
tmp_circle.remove();
tmp_circle.setPosition(pos.x, pos.y);
tmp_circle.setFillPatternImage(images.color);
tmp_circle.setFillPatternOffset(pos.x, pos.y);
circle_layer.add(tmp_circle);
}
}
},
anim = new Anim(null, function(){
colorArea();
}),
onPress = function() {
circle_layer.show();
//hitareas_layer.hide()
movable = true;
colorArea();
circle_layer.draw();
anim.start();
}, onRelease = function() {
anim.stop();
circle_layer.draw();
tmp_circle.remove();
circle_layer.hide();
//hitareas_layer.show()
movable = false;
c=0;
};
//hit_areas = setHitAreas(bn_layer);
bn_layer.add(bn);
tmp_circle = addCircle(100, {
x : 50,
y : 50
});
hit_areas = setHitAreas(hitareas_layer, images.color);
bn_layer.on(HitArea.HITTED, function(){
console.log("this");
})
//descomentar si queremos efecto al mover el rat�n
//stage.on("mousemove touchmove", colorArea);
stage.on("mousedown touchstart", onPress);
stage.on("mouseup touchend", onRelease);
stage.add(bn_layer);
stage.add(circle_layer);
stage.add(hitareas_layer);
circle_layer.hide();
}
Place if (movable) condition outside of onMove() function, this way you`ll not check every time for this capability:
if (movable) {
var onMove = function() {
var pos = getMousePosition(stage);
circle_layer.draw();
tmp_circle.remove();
tmp_circle.setPosition(pos.x, pos.y)
tmp_circle.setFillPatternImage(images.color);
tmp_circle.setFillPatternOffset(pos.x, pos.y);
circle_layer.add(tmp_circle);
}
}

Mootools mousewheel event, and adding an onComplete

I'm using the following class along with Mootools to create custom scrolling areas on a site. It includes a mousewheel event. I need to be able to fire an onComplete once the scroller comes to a stop after using the mousewheel. So say you swipe the mousewheel to scroll, I need to fire an oncomplete once the scrolling content comes to a stop.
Suggestions?
var ScrollBar = new Class({
Implements: [Events, Options],
options: {
wheel: (Browser.safari5) ? 1 : 20
},
initialize: function(main, options) {
this.setOptions(options);
this.dragging = false;
this.inside = false;
this.main = $(main);
this.content = this.main.getFirst();
this.vScrollbar = new Element('div', {
'class': 'scrollbar'
}).inject(this.content, 'after');
this.vTrack = new Element('div', {
'class': 'track'
}).inject(this.vScrollbar);
this.vThumb = new Element('div', {
'class': 'handle'
}).inject(this.vTrack);
this.bound = {
'vStart': this.vStart.bind(this),
'end': this.end.bind(this),
'vDrag': this.vDrag.bind(this),
'wheel': this.wheel.bind(this),
'vPage': this.vPage.bind(this)
};
// set scrollarea mousein/out hide of scrollbar
this.vScrollbar.set('tween', {
duration: 200,
transition: 'cubic:out'
});
this.main.addEvent('mouseenter', function(event){
this.inside = true;
this.vScrollbar.get('tween').cancel();
this.vScrollbar.tween('width', 12);
}.bind(this));
this.main.addEvent('mouseleave', function(event){
this.inside = false;
if (!this.dragging) {
this.vScrollbar.get('tween').cancel();
this.vScrollbar.tween('width', 0);
}
}.bind(this));
this.vPosition = {};
this.vMouse = {};
this.update();
this.attach();
this.scrollContent = new Fx.Scroll(this.content, {
duration: 500,
transition: Fx.Transitions.Cubic.easeOut,
onComplete: function(){
Blinds.updateImages();
}
});
this.scrollThumb = new Fx.Morph(this.vThumb, {
duration: 500,
transition: Fx.Transitions.Cubic.easeOut
});
},
update: function() {
var panel_id = (this.content.getFirst()) ? this.content.getFirst().get('id') : '';
if ((this.content.scrollHeight <= this.main.offsetHeight) || panel_id === 'random-doodle' || (this.content.getFirst() && this.content.getFirst().hasClass('collapsed'))) {
this.main.addClass('noscroll');
return false;
}
else { this.main.removeClass('noscroll'); }
this.vContentSize = this.content.offsetHeight;
this.vContentScrollSize = this.content.scrollHeight;
this.vTrackSize = this.vTrack.offsetHeight;
this.vContentRatio = this.vContentSize / this.vContentScrollSize;
this.vThumbSize = 200;
this.vThumb.setStyle('height', this.vThumbSize);
this.vScrollRatio = this.vContentScrollSize / (this.vTrackSize - this.vThumbSize) - this.vContentRatio * (this.vContentScrollSize / (this.vTrackSize - this.vThumbSize));
this.vUpdateThumbFromContentScroll();
this.vUpdateContentFromThumbPosition();
},
vUpdateContentFromThumbPosition: function() {
this.content.scrollTop = this.vPosition.now * this.vScrollRatio;
},
vUpdateContentFromThumbPosition2: function() {
var pos = this.vPosition.now * this.vScrollRatio;
this.scrollContent.start(0, pos);
},
vUpdateThumbFromContentScroll: function() {
this.vPosition.now = (this.content.scrollTop / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));
this.vThumb.setStyle('top', this.vPosition.now);
},
vUpdateThumbFromContentScroll2: function(pos) {
this.vPosition.now = (this.content.scrollTopNew / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));
this.scrollThumb.start({
'top': this.vPosition.now
});
},
attach: function() {
if (this.options.wheel) { this.content.addEvent('mousewheel', this.bound.wheel); }
this.vThumb.addEvent('mousedown', this.bound.vStart);
this.vTrack.addEvent('mouseup', this.bound.vPage);
},
wheel: function(event) {
this.content.scrollTop -= event.wheel * this.options.wheel;
this.vUpdateThumbFromContentScroll();
event.stop();
},
scrollTo: function(pos){
myInstance = this;
this.content.scrollTopNew = pos;
this.scrollContent.start(0, this.content.scrollTopNew);
myInstance.vUpdateThumbFromContentScroll2(pos);
},
vPage: function(event) {
// if scrolling up
if (event.page.y > this.vThumb.getPosition().y) {
myInstance = this;
this.content.scrollTopNew = this.content.scrollTop.toInt() + this.content.offsetHeight.toInt();
this.scrollContent.start(0, this.content.scrollTopNew);
}
// if scrolling down
else {
myInstance = this;
this.content.scrollTopNew = this.content.scrollTop.toInt() - this.content.offsetHeight.toInt();
this.scrollContent.start(0, this.content.scrollTopNew);
}
myInstance.vUpdateThumbFromContentScroll2(event.page.y);
event.stop();
},
vStart: function(event) {
this.dragging = true;
this.vMouse.start = event.page.y;
this.vPosition.start = this.vThumb.getStyle('top').toInt();
document.addEvent('mousemove', this.bound.vDrag);
document.addEvent('mouseup', this.bound.end);
this.vThumb.addEvent('mouseup', this.bound.end);
event.stop();
},
end: function(event) {
this.dragging = false;
if (!this.inside) {
this.vScrollbar.get('tween').cancel();
this.vScrollbar.tween('width', 0);
}
document.removeEvent('mousemove', this.bound.vDrag);
document.removeEvent('mouseup', this.bound.end);
this.vThumb.removeEvent('mouseup', this.bound.end);
Blinds.updateImages();
event.stop();
},
vDrag: function(event) {
this.vMouse.now = event.page.y;
this.vPosition.now = (this.vPosition.start + (this.vMouse.now - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));
this.vUpdateContentFromThumbPosition();
this.vUpdateThumbFromContentScroll();
event.stop();
}
});
You could modify your wheel function to reset a delayed function timer (after clearing any previous timers that might still exist). To have the 'autoComplete' fired 1000ms after the last wheel event, try something like this:
wheel: function(event) {
this.content.scrollTop -= event.wheel * this.options.wheel;
this.vUpdateThumbFromContentScroll();
// clear the timer from previous wheel events, if it still exists
if(this.timer) {
clearTimeout(timer);
}
this.timer = function() {this.fireEvent('autoComplete');}.delay(1000, this);
event.stop();
},

Categories