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();
}
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();
},