Improve scrolls behaviour

Migrate to nanoscroller 0.8.0
Fixed history scrollbar. Closes #341
Fixed mobile view infinite scroll. Closes #353
This commit is contained in:
Igor Zhukov 2014-06-30 17:30:42 +04:00
parent bafab6e250
commit afc066e3bc
11 changed files with 293 additions and 263 deletions

View File

@ -804,7 +804,7 @@ a.tg_radio_on:hover i.icon-radio {
} }
.im_dialogs_col .nano > .pane { .im_dialogs_col .nano > .nano-pane {
background : rgba(0,0,0,0.0); background : rgba(0,0,0,0.0);
width : 12px; width : 12px;
right: 0px; right: 0px;
@ -820,7 +820,7 @@ a.tg_radio_on:hover i.icon-radio {
-o-transition : none; -o-transition : none;
transition : none; transition : none;
} }
.im_dialogs_col .nano > .pane > .slider { .im_dialogs_col .nano > .nano-pane > .nano-slider {
background: #A5B1B9; background: #A5B1B9;
margin: 0 5px; margin: 0 5px;
-moz-border-radius : 0; -moz-border-radius : 0;
@ -1076,9 +1076,9 @@ a.im_dialog_selected .im_dialog_date {
.im_history_col { .im_history_col {
} }
.im_history_col .nano > .pane, .im_history_col .nano > .nano-pane,
.contacts_modal_col .nano > .pane, .contacts_modal_col .nano > .nano-pane,
.im_dialogs_modal_col .nano > .pane { .im_dialogs_modal_col .nano > .nano-pane {
background : rgba(3,36,64,0.08); background : rgba(3,36,64,0.08);
width : 9px; width : 9px;
right: 0; right: 0;
@ -1097,12 +1097,12 @@ a.im_dialog_selected .im_dialog_date {
transition : none; transition : none;
} }
.contacts_modal_col .nano > .pane { .contacts_modal_col .nano > .nano-pane {
width: 6px; width: 6px;
right: 7px; right: 7px;
} }
.im_dialogs_modal_col .nano > .pane { .im_dialogs_modal_col .nano > .nano-pane {
width: 6px; width: 6px;
right: 2px; right: 2px;
} }
@ -1110,13 +1110,13 @@ a.im_dialog_selected .im_dialog_date {
padding: 0 12px 0 12px; padding: 0 12px 0 12px;
} }
.im_history_col .nano > .pane { .im_history_col .nano > .nano-pane {
top: 10px; top: 10px;
right: 8px; right: 8px;
} }
.im_history_col .nano > .pane > .slider, .im_history_col .nano > .nano-pane > .nano-slider,
.contacts_modal_col .nano > .pane > .slider, .contacts_modal_col .nano > .nano-pane > .nano-slider,
.im_dialogs_modal_col .nano > .pane > .slider { .im_dialogs_modal_col .nano > .nano-pane > .nano-slider {
background : rgba(3,46,79,0.22); background : rgba(3,46,79,0.22);
margin: 0; margin: 0;
-moz-border-radius : 2px; -moz-border-radius : 2px;
@ -1219,7 +1219,7 @@ a.im_dialog_selected .im_dialog_date {
.im_history_wrap { .im_history_wrap {
overflow: hidden; /*overflow: hidden;*/
/*overflow-y: scroll;*/ /*overflow-y: scroll;*/
} }
.im_history_scrollable_wrap { .im_history_scrollable_wrap {
@ -1251,11 +1251,13 @@ a.im_dialog_selected .im_dialog_date {
} }
.im_history_typing_wrap { .im_history_typing_wrap {
margin-top: 13px; /*margin-top: 13px;*/
height: 18px; /*height: 18px;*/
line-height: 18px; line-height: 18px;
width: 100%; width: 100%;
margin-bottom: 13px; height: 49px;
/*margin-bottom: 13px;*/
padding: 13px 0 18px;
overflow: hidden; overflow: hidden;
-webkit-user-select: none; -webkit-user-select: none;
} }
@ -2447,11 +2449,11 @@ img.chat_modal_participant_photo {
} }
.emoji-menu .nano > .pane { .emoji-menu .nano > .nano-pane {
background : rgba(255,255,255,0.0); background : rgba(255,255,255,0.0);
right: -2px; right: -2px;
} }
.emoji-menu .nano > .pane > .slider { .emoji-menu .nano > .nano-pane > .nano-slider {
background: #d1d1d1; background: #d1d1d1;
margin: 0 3px 0 4px; margin: 0 3px 0 4px;
} }
@ -3261,7 +3263,7 @@ ce671b orange
padding: 14px 0; padding: 14px 0;
} }
.countries_modal_col .nano > .pane { .countries_modal_col .nano > .nano-pane {
background : rgba(3,36,64,0.08); background : rgba(3,36,64,0.08);
width : 3px; width : 3px;
right: 6px; right: 6px;
@ -3274,7 +3276,7 @@ ce671b orange
-webkit-border-radius : 0; -webkit-border-radius : 0;
border-radius : 0; border-radius : 0;
} }
.countries_modal_col .nano > .pane > .slider { .countries_modal_col .nano > .nano-pane > .nano-slider {
background : rgba(3,46,79,0.22); background : rgba(3,46,79,0.22);
margin: 0; margin: 0;
-moz-border-radius : 0; -moz-border-radius : 0;

View File

@ -135,7 +135,9 @@ html {
font-size: 14px; font-size: 14px;
color: #FFF; color: #FFF;
white-space: nowrap; white-space: nowrap;
max-width: 100px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
margin: 0; margin: 0;
} }
.navbar-quick-media-back h4 { .navbar-quick-media-back h4 {
@ -268,14 +270,14 @@ html {
margin: 0 5px; margin: 0 5px;
padding-bottom: 18px; padding-bottom: 18px;
} }
.im_history_col .nano > .pane { .im_history_col .nano > .nano-pane {
top: 3px; top: 3px;
right: 3px; right: 3px;
width: 6px; width: 6px;
} }
.im_history_col .nano > .pane > .slider, .im_history_col .nano > .nano-pane > .nano-slider,
.contacts_modal_col .nano > .pane > .slider, .contacts_modal_col .nano > .nano-pane > .nano-slider,
.im_dialogs_modal_col .nano > .pane > .slider { .im_dialogs_modal_col .nano > .nano-pane > .nano-slider {
background : rgba(3,46,79,0.22); background : rgba(3,46,79,0.22);
border-radius: 3px; border-radius: 3px;
margin: 0; margin: 0;
@ -305,11 +307,11 @@ html {
.im_dialogs_col { .im_dialogs_col {
margin-right: 0; margin-right: 0;
} }
.im_page_peer_not_selected .im_dialogs_col_wrap .pane { .im_page_peer_not_selected .im_dialogs_col_wrap .nano-pane {
width: 6px; width: 6px;
right: 3px; right: 3px;
} }
.im_page_peer_not_selected .im_dialogs_col_wrap .pane > .slider { .im_page_peer_not_selected .im_dialogs_col_wrap .nano-pane > .nano-slider {
background : rgba(3,46,79,0.22); background : rgba(3,46,79,0.22);
border-radius: 3px; border-radius: 3px;
margin: 0; margin: 0;

View File

@ -254,6 +254,7 @@ angular.module('myApp.directives', ['myApp.filters'])
}); });
$(scrollableWrap).on('scroll', function (e) { $(scrollableWrap).on('scroll', function (e) {
if (!element.is(':visible')) return;
// console.log('scroll', moreNotified); // console.log('scroll', moreNotified);
if (!moreNotified && scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) { if (!moreNotified && scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) {
// console.log('emit need more'); // console.log('emit need more');
@ -421,13 +422,16 @@ angular.module('myApp.directives', ['myApp.filters'])
if (!atBottom && !options.my) { if (!atBottom && !options.my) {
return; return;
} }
var curAnimated = animated && !$rootScope.idle.isIDLE, var curAnimated = animated &&
!$rootScope.idle.isIDLE &&
historyMessagesEl.clientHeight > 0,
wasH; wasH;
if (!curAnimated) {
if (curAnimated) {
wasH = scrollableWrap.scrollHeight;
} else {
$(scrollable).css({bottom: 0}); $(scrollable).css({bottom: 0});
$(scrollableWrap).addClass('im_history_to_bottom'); $(scrollableWrap).addClass('im_history_to_bottom');
} else {
wasH = scrollableWrap.scrollHeight;
} }
onContentLoaded(function () { onContentLoaded(function () {
@ -450,7 +454,6 @@ angular.module('myApp.directives', ['myApp.filters'])
$(scrollable).css({bottom: ''}); $(scrollable).css({bottom: ''});
scrollableWrap.scrollTop = scrollableWrap.scrollHeight; scrollableWrap.scrollTop = scrollableWrap.scrollHeight;
updateBottomizer(); updateBottomizer();
$(historyWrap).nanoScroller();
} }
}); });
}); });
@ -501,15 +504,16 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.$on('ui_history_prepend', function () { $scope.$on('ui_history_prepend', function () {
var sh = scrollableWrap.scrollHeight, var sh = scrollableWrap.scrollHeight,
st = scrollableWrap.scrollTop, st = scrollableWrap.scrollTop,
pr = parseInt($(scrollableWrap).css('paddingRight')),
ch = scrollableWrap.clientHeight; ch = scrollableWrap.clientHeight;
$(scrollableWrap).addClass('im_history_to_bottom'); $(scrollableWrap).addClass('im_history_to_bottom');
scrollableWrap.scrollHeight; // Some strange Chrome bug workaround scrollableWrap.scrollHeight; // Some strange Chrome bug workaround
$(scrollable).css({bottom: -(sh - st - ch)}); $(scrollable).css({bottom: -(sh - st - ch), marginLeft: -Math.ceil(pr / 2)});
onContentLoaded(function () { onContentLoaded(function () {
$(scrollableWrap).removeClass('im_history_to_bottom'); $(scrollableWrap).removeClass('im_history_to_bottom');
$(scrollable).css({bottom: ''}); $(scrollable).css({bottom: '', marginLeft: ''});
scrollableWrap.scrollTop = st + scrollableWrap.scrollHeight - sh; scrollableWrap.scrollTop = st + scrollableWrap.scrollHeight - sh;
updateBottomizer(); updateBottomizer();
@ -569,10 +573,9 @@ angular.module('myApp.directives', ['myApp.filters'])
var atBottom = true; var atBottom = true;
$(scrollableWrap).on('scroll', function (e) { $(scrollableWrap).on('scroll', function (e) {
if ($(scrollableWrap).hasClass('im_history_to_bottom')) { if (!element.is(':visible') ||
return cancelEvent(e); $(scrollableWrap).hasClass('im_history_to_bottom') ||
} curAnimation) {
if (curAnimation) {
return; return;
} }
atBottom = scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight; atBottom = scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight;
@ -607,9 +610,6 @@ angular.module('myApp.directives', ['myApp.filters'])
$(historyWrap).css({ $(historyWrap).css({
height: historyH height: historyH
}); });
$(historyEl).css({
minHeight: historyH - 44
});
updateBottomizer(); updateBottomizer();
@ -626,8 +626,9 @@ angular.module('myApp.directives', ['myApp.filters'])
function updateBottomizer () { function updateBottomizer () {
$(historyMessagesEl).css({marginTop: 0}); $(historyMessagesEl).css({marginTop: 0});
if (historyMessagesEl.offsetHeight > 0 && historyMessagesEl.offsetHeight <= scrollableWrap.offsetHeight) { var marginTop = scrollableWrap.offsetHeight - historyMessagesEl.offsetHeight - 20 - 49;
$(historyMessagesEl).css({marginTop: (scrollableWrap.offsetHeight - historyMessagesEl.offsetHeight - 20 - 44) + 'px'}); if (historyMessagesEl.offsetHeight > 0 && marginTop > 0) {
$(historyMessagesEl).css({marginTop: marginTop});
} }
$(historyWrap).nanoScroller(); $(historyWrap).nanoScroller();
} }
@ -741,7 +742,8 @@ angular.module('myApp.directives', ['myApp.filters'])
lastLength; lastLength;
$(editorElement).on('keyup', function (e) { $(editorElement).on('keyup', function (e) {
var now = tsNow(), var now = tsNow(),
length = (editorElement[richTextarea ? 'innerText' : 'value']).length; length = (editorElement[richTextarea ? 'textContent' : 'value']).length;
if (now - lastTyping > 5000 && length != lastLength) { if (now - lastTyping > 5000 && length != lastLength) {
lastTyping = now; lastTyping = now;
@ -1405,6 +1407,7 @@ angular.module('myApp.directives', ['myApp.filters'])
moreNotified = false; moreNotified = false;
$(scrollableWrap).on('scroll', function (e) { $(scrollableWrap).on('scroll', function (e) {
if (!element.is(':visible')) return;
if (!moreNotified && if (!moreNotified &&
scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) { scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) {
moreNotified = true; moreNotified = true;

View File

@ -15,7 +15,7 @@
<div my-contacts-list class="contacts_modal_col"> <div my-contacts-list class="contacts_modal_col">
<div class="contacts_wrap nano" my-infinite-scroller> <div class="contacts_wrap nano" my-infinite-scroller>
<div class="contacts_scrollable_wrap content"> <div class="contacts_scrollable_wrap nano-content">
<ul class="contacts_modal_members_list nav nav-pills nav-stacked"> <ul class="contacts_modal_members_list nav nav-pills nav-stacked">

View File

@ -12,7 +12,7 @@
<div class="countries_modal_col" my-countries-list> <div class="countries_modal_col" my-countries-list>
<div class="countries_wrap nano" my-infinite-scroller> <div class="countries_wrap nano" my-infinite-scroller>
<div class="countries_scrollable_wrap content"> <div class="countries_scrollable_wrap nano-content">
<ul class="countries_modal_members_list nav nav-pills nav-stacked"> <ul class="countries_modal_members_list nav nav-pills nav-stacked">

View File

@ -34,7 +34,7 @@
<div my-dialogs-list class="im_dialogs_col"> <div my-dialogs-list class="im_dialogs_col">
<div class="im_dialogs_wrap nano"> <div class="im_dialogs_wrap nano">
<div class="im_dialogs_scrollable_wrap content"> <div class="im_dialogs_scrollable_wrap nano-content">
<div class="im_dialogs_empty_wrap" ng-if="isEmpty.contacts"> <div class="im_dialogs_empty_wrap" ng-if="isEmpty.contacts">
<h3 class="im_dialogs_empty_header">No contacts yet</h3> <h3 class="im_dialogs_empty_header">No contacts yet</h3>
@ -153,7 +153,7 @@
<div class="im_history_wrap nano"> <div class="im_history_wrap nano">
<div class="im_history_scrollable_wrap content"> <div class="im_history_scrollable_wrap nano-content">
<div class="im_history_scrollable"> <div class="im_history_scrollable">
<div class="im_history" ng-class="{im_history_selectable: selectActions}"> <div class="im_history" ng-class="{im_history_selectable: selectActions}">

View File

@ -15,7 +15,7 @@
</div> </div>
<div my-dialogs-list modal="true" class="im_dialogs_modal_col"> <div my-dialogs-list modal="true" class="im_dialogs_modal_col">
<div class="im_dialogs_wrap nano"> <div class="im_dialogs_wrap nano">
<div class="im_dialogs_scrollable_wrap content"> <div class="im_dialogs_scrollable_wrap nano-content">
<ul class="nav nav-pills nav-stacked"> <ul class="nav nav-pills nav-stacked">
<li class="im_dialog_wrap" my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs track by dialogMessage.peerID"></li> <li class="im_dialog_wrap" my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs track by dialogMessage.peerID"></li>
</ul> </ul>

View File

@ -15,7 +15,7 @@
<div my-contacts-list class="contacts_modal_col"> <div my-contacts-list class="contacts_modal_col">
<div class="contacts_wrap nano" my-infinite-scroller> <div class="contacts_wrap nano" my-infinite-scroller>
<div class="contacts_scrollable_wrap content"> <div class="contacts_scrollable_wrap nano-content">
<ul class="contacts_modal_members_list nav nav-pills nav-stacked"> <ul class="contacts_modal_members_list nav nav-pills nav-stacked">

View File

@ -488,7 +488,7 @@
'<td><a class="emoji-menu-tab icon-grid"></a></td>' + '<td><a class="emoji-menu-tab icon-grid"></a></td>' +
'</tr></table>').appendTo(this.$itemsTailWrap); '</tr></table>').appendTo(this.$itemsTailWrap);
this.$itemsWrap = $('<div class="emoji-items-wrap nano"></div>').appendTo(this.$itemsTailWrap); this.$itemsWrap = $('<div class="emoji-items-wrap nano"></div>').appendTo(this.$itemsTailWrap);
this.$items = $('<div class="emoji-items content">').appendTo(this.$itemsWrap); this.$items = $('<div class="emoji-items nano-content">').appendTo(this.$itemsWrap);
$('<div class="emoji-menu-tail">').appendTo(this.$menu); $('<div class="emoji-menu-tail">').appendTo(this.$menu);
/*! MODIFICATION END */ /*! MODIFICATION END */

View File

@ -5,7 +5,7 @@
height : 100%; height : 100%;
overflow : hidden; overflow : hidden;
} }
.nano .content { .nano > .nano-content {
position : absolute; position : absolute;
overflow : scroll; overflow : scroll;
overflow-x : hidden; overflow-x : hidden;
@ -14,23 +14,17 @@
bottom : 0; bottom : 0;
left : 0; left : 0;
} }
.nano .content:focus { .nano > .nano-content:focus {
outline: thin dotted; outline: thin dotted;
} }
.nano .content::-webkit-scrollbar { .nano > .nano-content::-webkit-scrollbar {
visibility: hidden; visibility: hidden;
} }
.has-scrollbar .content::-webkit-scrollbar { .has-scrollbar > .nano-content::-webkit-scrollbar {
visibility: visible; visibility: visible;
} }
.nano .content::-moz-scrollbar { .nano > .nano-pane {
visibility: hidden; background : rgba(0,0,0,.25);
}
.has-scrollbar .content::-moz-scrollbar {
visibility: visible;
}
.nano > .pane {
background : rgba(0,0,0,.1);
position : absolute; position : absolute;
width : 10px; width : 10px;
right : 0; right : 0;
@ -46,16 +40,16 @@
-webkit-border-radius : 5px; -webkit-border-radius : 5px;
border-radius : 5px; border-radius : 5px;
} }
.nano > .pane > .slider { .nano > .nano-pane > .nano-slider {
background: #444; background: #444;
background: rgba(0,0,0,.4); background: rgba(0,0,0,.5);
position : relative; position : relative;
margin : 0 1px; margin : 0 1px;
-moz-border-radius : 3px; -moz-border-radius : 3px;
-webkit-border-radius : 3px; -webkit-border-radius : 3px;
border-radius : 3px; border-radius : 3px;
} }
.nano:hover > .pane, .pane.active, .pane.flashed { .nano:hover > .nano-pane, .nano-pane.active, .nano-pane.flashed {
visibility : visible\9; /* Target only IE7 and IE8 with this hack */ visibility : visible\9; /* Target only IE7 and IE8 with this hack */
opacity : 0.99; opacity : 0.99;
} }

View File

@ -1,266 +1,266 @@
/*! nanoScrollerJS - v0.7.6 - 2013 /*! nanoScrollerJS - v0.8.0 - 2014
* http://jamesflorentino.github.com/nanoScrollerJS/ * http://jamesflorentino.github.com/nanoScrollerJS/
* Copyright (c) 2013 James Florentino; Licensed MIT */ * Copyright (c) 2014 James Florentino; Licensed MIT */
(function($, window, document) { (function($, window, document) {
"use strict"; "use strict";
var BROWSER_IS_IE7, BROWSER_SCROLLBAR_WIDTH, DOMSCROLL, DOWN, DRAG, KEYDOWN, KEYUP, MOUSEDOWN, MOUSEMOVE, MOUSEUP, MOUSEWHEEL, NanoScroll, PANEDOWN, RESIZE, SCROLL, SCROLLBAR, TOUCHMOVE, UP, WHEEL, cAF, defaults, getBrowserScrollbarWidth, hasTransform, isFFWithBuggyScrollbar, rAF, transform, _elementStyle, _prefixStyle, _vendor; var BROWSER_IS_IE7, BROWSER_SCROLLBAR_WIDTH, DOMSCROLL, DOWN, DRAG, KEYDOWN, KEYUP, MOUSEDOWN, MOUSEMOVE, MOUSEUP, MOUSEWHEEL, NanoScroll, PANEDOWN, RESIZE, SCROLL, SCROLLBAR, TOUCHMOVE, UP, WHEEL, cAF, defaults, getBrowserScrollbarWidth, hasTransform, isFFWithBuggyScrollbar, rAF, transform, _elementStyle, _prefixStyle, _vendor;
defaults = { defaults = {
/** /**
a classname for the pane element. a classname for the pane element.
@property paneClass @property paneClass
@type String @type String
@default 'pane' @default 'nano-pane'
*/ */
paneClass: 'nano-pane',
paneClass: 'pane',
/** /**
a classname for the slider element. a classname for the slider element.
@property sliderClass @property sliderClass
@type String @type String
@default 'slider' @default 'nano-slider'
*/ */
sliderClass: 'nano-slider',
sliderClass: 'slider',
/** /**
a classname for the content element. a classname for the content element.
@property contentClass @property contentClass
@type String @type String
@default 'content' @default 'nano-content'
*/ */
contentClass: 'nano-content',
contentClass: 'content',
/** /**
a setting to enable native scrolling in iOS devices. a setting to enable native scrolling in iOS devices.
@property iOSNativeScrolling @property iOSNativeScrolling
@type Boolean @type Boolean
@default false @default false
*/ */
iOSNativeScrolling: false, iOSNativeScrolling: false,
/** /**
a setting to prevent the rest of the page being a setting to prevent the rest of the page being
scrolled when user scrolls the `.content` element. scrolled when user scrolls the `.content` element.
@property preventPageScrolling @property preventPageScrolling
@type Boolean @type Boolean
@default false @default false
*/ */
preventPageScrolling: false, preventPageScrolling: false,
/** /**
a setting to disable binding to the resize event. a setting to disable binding to the resize event.
@property disableResize @property disableResize
@type Boolean @type Boolean
@default false @default false
*/ */
disableResize: false, disableResize: false,
/** /**
a setting to make the scrollbar always visible. a setting to make the scrollbar always visible.
@property alwaysVisible @property alwaysVisible
@type Boolean @type Boolean
@default false @default false
*/ */
alwaysVisible: false, alwaysVisible: false,
/** /**
a default timeout for the `flash()` method. a default timeout for the `flash()` method.
@property flashDelay @property flashDelay
@type Number @type Number
@default 1500 @default 1500
*/ */
flashDelay: 1500, flashDelay: 1500,
/** /**
a minimum height for the `.slider` element. a minimum height for the `.slider` element.
@property sliderMinHeight @property sliderMinHeight
@type Number @type Number
@default 20 @default 20
*/ */
sliderMinHeight: 20, sliderMinHeight: 20,
/** /**
a maximum height for the `.slider` element. a maximum height for the `.slider` element.
@property sliderMaxHeight @property sliderMaxHeight
@type Number @type Number
@default null @default null
*/ */
sliderMaxHeight: null, sliderMaxHeight: null,
/** /**
an alternate document context. an alternate document context.
@property documentContext @property documentContext
@type Document @type Document
@default null @default null
*/ */
documentContext: null, documentContext: null,
/** /**
an alternate window context. an alternate window context.
@property windowContext @property windowContext
@type Window @type Window
@default null @default null
*/ */
windowContext: null windowContext: null
}; };
/** /**
@property SCROLLBAR @property SCROLLBAR
@type String @type String
@static @static
@final @final
@private @private
*/ */
SCROLLBAR = 'scrollbar'; SCROLLBAR = 'scrollbar';
/** /**
@property SCROLL @property SCROLL
@type String @type String
@static @static
@final @final
@private @private
*/ */
SCROLL = 'scroll'; SCROLL = 'scroll';
/** /**
@property MOUSEDOWN @property MOUSEDOWN
@type String @type String
@final @final
@private @private
*/ */
MOUSEDOWN = 'mousedown'; MOUSEDOWN = 'mousedown';
/** /**
@property MOUSEMOVE @property MOUSEMOVE
@type String @type String
@static @static
@final @final
@private @private
*/ */
MOUSEMOVE = 'mousemove'; MOUSEMOVE = 'mousemove';
/** /**
@property MOUSEWHEEL @property MOUSEWHEEL
@type String @type String
@final @final
@private @private
*/ */
MOUSEWHEEL = 'mousewheel'; MOUSEWHEEL = 'mousewheel';
/** /**
@property MOUSEUP @property MOUSEUP
@type String @type String
@static @static
@final @final
@private @private
*/ */
MOUSEUP = 'mouseup'; MOUSEUP = 'mouseup';
/** /**
@property RESIZE @property RESIZE
@type String @type String
@final @final
@private @private
*/ */
RESIZE = 'resize'; RESIZE = 'resize';
/** /**
@property DRAG @property DRAG
@type String @type String
@static @static
@final @final
@private @private
*/ */
DRAG = 'drag'; DRAG = 'drag';
/** /**
@property UP @property UP
@type String @type String
@static @static
@final @final
@private @private
*/ */
UP = 'up'; UP = 'up';
/** /**
@property PANEDOWN @property PANEDOWN
@type String @type String
@static @static
@final @final
@private @private
*/ */
PANEDOWN = 'panedown'; PANEDOWN = 'panedown';
/** /**
@property DOMSCROLL @property DOMSCROLL
@type String @type String
@static @static
@final @final
@private @private
*/ */
DOMSCROLL = 'DOMMouseScroll'; DOMSCROLL = 'DOMMouseScroll';
/** /**
@property DOWN @property DOWN
@type String @type String
@static @static
@final @final
@private @private
*/ */
DOWN = 'down'; DOWN = 'down';
/** /**
@property WHEEL @property WHEEL
@type String @type String
@static @static
@final @final
@private @private
*/ */
WHEEL = 'wheel'; WHEEL = 'wheel';
/** /**
@property KEYDOWN @property KEYDOWN
@type String @type String
@static @static
@final @final
@private @private
*/ */
KEYDOWN = 'keydown'; KEYDOWN = 'keydown';
/** /**
@property KEYUP @property KEYUP
@type String @type String
@static @static
@final @final
@private @private
*/ */
KEYUP = 'keyup'; KEYUP = 'keyup';
/** /**
@property TOUCHMOVE @property TOUCHMOVE
@type String @type String
@static @static
@final @final
@private @private
*/ */
TOUCHMOVE = 'touchmove'; TOUCHMOVE = 'touchmove';
/** /**
@property BROWSER_IS_IE7 @property BROWSER_IS_IE7
@type Boolean @type Boolean
@static @static
@final @final
@private @private
*/ */
BROWSER_IS_IE7 = window.navigator.appName === 'Microsoft Internet Explorer' && /msie 7./i.test(window.navigator.appVersion) && window.ActiveXObject; BROWSER_IS_IE7 = window.navigator.appName === 'Microsoft Internet Explorer' && /msie 7./i.test(window.navigator.appVersion) && window.ActiveXObject;
/** /**
@property BROWSER_SCROLLBAR_WIDTH @property BROWSER_SCROLLBAR_WIDTH
@type Number @type Number
@static @static
@default null @default null
@private @private
*/ */
BROWSER_SCROLLBAR_WIDTH = null; BROWSER_SCROLLBAR_WIDTH = null;
rAF = window.requestAnimationFrame; rAF = window.requestAnimationFrame;
cAF = window.cancelAnimationFrame; cAF = window.cancelAnimationFrame;
@ -288,14 +288,14 @@
}; };
transform = _prefixStyle('transform'); transform = _prefixStyle('transform');
hasTransform = transform !== false; hasTransform = transform !== false;
/** /**
Returns browser's native scrollbar width Returns browser's native scrollbar width
@method getBrowserScrollbarWidth @method getBrowserScrollbarWidth
@return {Number} the scrollbar width in pixels @return {Number} the scrollbar width in pixels
@static @static
@private @private
*/ */
getBrowserScrollbarWidth = function() { getBrowserScrollbarWidth = function() {
var outer, outerStyle, scrollbarWidth; var outer, outerStyle, scrollbarWidth;
outer = document.createElement('div'); outer = document.createElement('div');
@ -323,13 +323,13 @@
} }
return isOSXFF && +version > 23; return isOSXFF && +version > 23;
}; };
/** /**
@class NanoScroll @class NanoScroll
@param element {HTMLElement|Node} the main element @param element {HTMLElement|Node} the main element
@param options {Object} nanoScroller's options @param options {Object} nanoScroller's options
@constructor @constructor
*/ */
NanoScroll = (function() { NanoScroll = (function() {
function NanoScroll(el, options) { function NanoScroll(el, options) {
this.el = el; this.el = el;
@ -341,6 +341,7 @@
this.$content = this.$el.children("." + options.contentClass); this.$content = this.$el.children("." + options.contentClass);
this.$content.attr('tabindex', this.options.tabIndex || 0); this.$content.attr('tabindex', this.options.tabIndex || 0);
this.content = this.$content[0]; this.content = this.$content[0];
this.previousPosition = 0;
if (this.options.iOSNativeScrolling && (this.el.style.WebkitOverflowScrolling != null)) { if (this.options.iOSNativeScrolling && (this.el.style.WebkitOverflowScrolling != null)) {
this.nativeScrolling(); this.nativeScrolling();
} else { } else {
@ -351,15 +352,15 @@
this.reset(); this.reset();
} }
/** /**
Prevents the rest of the page being scrolled Prevents the rest of the page being scrolled
when user scrolls the `.content` element. when user scrolls the `.nano-content` element.
@method preventScrolling @method preventScrolling
@param event {Event} @param event {Event}
@param direction {String} Scroll direction (up or down) @param direction {String} Scroll direction (up or down)
@private @private
*/ */
NanoScroll.prototype.preventScrolling = function(e, direction) { NanoScroll.prototype.preventScrolling = function(e, direction) {
if (!this.isActive) { if (!this.isActive) {
@ -379,12 +380,12 @@
} }
}; };
/** /**
Enable iOS native scrolling Enable iOS native scrolling
@method nativeScrolling @method nativeScrolling
@private @private
*/ */
NanoScroll.prototype.nativeScrolling = function() { NanoScroll.prototype.nativeScrolling = function() {
this.$content.css({ this.$content.css({
@ -394,44 +395,48 @@
this.isActive = true; this.isActive = true;
}; };
/** /**
Updates those nanoScroller properties that Updates those nanoScroller properties that
are related to current scrollbar position. are related to current scrollbar position.
@method updateScrollValues @method updateScrollValues
@private @private
*/ */
NanoScroll.prototype.updateScrollValues = function() { NanoScroll.prototype.updateScrollValues = function() {
var content; var content, direction;
content = this.content; content = this.content;
this.maxScrollTop = content.scrollHeight - content.clientHeight; this.maxScrollTop = content.scrollHeight - content.clientHeight;
this.prevScrollTop = this.contentScrollTop || 0; this.prevScrollTop = this.contentScrollTop || 0;
this.contentScrollTop = content.scrollTop; this.contentScrollTop = content.scrollTop;
direction = this.contentScrollTop > this.previousPosition ? "down" : this.contentScrollTop < this.previousPosition ? "up" : "same";
this.previousPosition = this.contentScrollTop;
if (direction !== "same") {
this.$el.trigger('update', {
position: this.contentScrollTop,
maximum: this.maxScrollTop,
direction: direction
});
}
if (!this.iOSNativeScrolling) { if (!this.iOSNativeScrolling) {
// console.log(this.maxScrollTop, this.contentScrollTop, this.maxSliderTop, this.maxScrollTop);
// console.trace();
this.maxSliderTop = this.paneHeight - this.sliderHeight; this.maxSliderTop = this.paneHeight - this.sliderHeight;
this.sliderTop = this.maxScrollTop === 0 ? 0 : this.contentScrollTop * this.maxSliderTop / this.maxScrollTop; this.sliderTop = this.maxScrollTop === 0 ? 0 : this.contentScrollTop * this.maxSliderTop / this.maxScrollTop;
} }
}; };
/** /**
Updates CSS styles for current scroll position. Updates CSS styles for current scroll position.
Uses CSS 2d transfroms and `window.requestAnimationFrame` if available. Uses CSS 2d transfroms and `window.requestAnimationFrame` if available.
@method setOnScrollStyles @method setOnScrollStyles
@private @private
*/ */
NanoScroll.prototype.setOnScrollStyles = function() { NanoScroll.prototype.setOnScrollStyles = function() {
var cssValue, var cssValue;
_this = this;
if (hasTransform) { if (hasTransform) {
cssValue = {}; cssValue = {};
cssValue[transform] = "translate(0, " + this.sliderTop + "px)"; cssValue[transform] = "translate(0, " + this.sliderTop + "px)";
// console.log(this.sliderTop, cssValue, this.scrollRAF, rAF);
} else { } else {
cssValue = { cssValue = {
top: this.sliderTop top: this.sliderTop
@ -439,109 +444,122 @@
} }
if (rAF) { if (rAF) {
if (!this.scrollRAF) { if (!this.scrollRAF) {
this.scrollRAF = rAF(function() { this.scrollRAF = rAF((function(_this) {
// console.log('raf called', cssValue); return function() {
_this.scrollRAF = null; _this.scrollRAF = null;
_this.slider.css(cssValue); _this.slider.css(cssValue);
}); };
})(this));
} }
} else { } else {
this.slider.css(cssValue); this.slider.css(cssValue);
} }
}; };
/** /**
Creates event related methods Creates event related methods
@method createEvents @method createEvents
@private @private
*/ */
NanoScroll.prototype.createEvents = function() { NanoScroll.prototype.createEvents = function() {
var _this = this;
this.events = { this.events = {
down: function(e) { down: (function(_this) {
_this.isBeingDragged = true; return function(e) {
_this.offsetY = e.pageY - _this.slider.offset().top; _this.isBeingDragged = true;
_this.pane.addClass('active'); _this.offsetY = e.pageY - _this.slider.offset().top;
_this.doc.bind(MOUSEMOVE, _this.events[DRAG]).bind(MOUSEUP, _this.events[UP]); _this.pane.addClass('active');
return false; _this.doc.bind(MOUSEMOVE, _this.events[DRAG]).bind(MOUSEUP, _this.events[UP]);
}, return false;
drag: function(e) { };
_this.sliderY = e.pageY - _this.$el.offset().top - _this.offsetY; })(this),
_this.scroll(); drag: (function(_this) {
_this.updateScrollValues(); return function(e) {
if (_this.contentScrollTop >= _this.maxScrollTop && _this.prevScrollTop !== _this.maxScrollTop) { _this.sliderY = e.pageY - _this.$el.offset().top - _this.offsetY;
_this.$el.trigger('scrollend'); _this.scroll();
} else if (_this.contentScrollTop === 0 && _this.prevScrollTop !== 0) { if (_this.contentScrollTop >= _this.maxScrollTop && _this.prevScrollTop !== _this.maxScrollTop) {
_this.$el.trigger('scrolltop');
}
return false;
},
up: function(e) {
_this.isBeingDragged = false;
_this.pane.removeClass('active');
_this.doc.unbind(MOUSEMOVE, _this.events[DRAG]).unbind(MOUSEUP, _this.events[UP]);
return false;
},
resize: function(e) {
_this.reset();
},
panedown: function(e) {
_this.sliderY = (e.offsetY || e.originalEvent.layerY) - (_this.sliderHeight * 0.5);
_this.scroll();
_this.events.down(e);
return false;
},
scroll: function(e) {
if (_this.isBeingDragged) {
return;
}
_this.updateScrollValues();
if (!_this.iOSNativeScrolling) {
_this.sliderY = _this.sliderTop;
_this.setOnScrollStyles();
}
if (e == null) {
return;
}
if (_this.contentScrollTop >= _this.maxScrollTop) {
if (_this.options.preventPageScrolling) {
_this.preventScrolling(e, DOWN);
}
if (_this.prevScrollTop !== _this.maxScrollTop) {
_this.$el.trigger('scrollend'); _this.$el.trigger('scrollend');
} } else if (_this.contentScrollTop === 0 && _this.prevScrollTop !== 0) {
} else if (_this.contentScrollTop === 0) {
if (_this.options.preventPageScrolling) {
_this.preventScrolling(e, UP);
}
if (_this.prevScrollTop !== 0) {
_this.$el.trigger('scrolltop'); _this.$el.trigger('scrolltop');
} }
} return false;
}, };
wheel: function(e) { })(this),
var delta; up: (function(_this) {
if (e == null) { return function(e) {
return; _this.isBeingDragged = false;
} _this.pane.removeClass('active');
delta = e.delta || e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail || (e.originalEvent && -e.originalEvent.detail); _this.doc.unbind(MOUSEMOVE, _this.events[DRAG]).unbind(MOUSEUP, _this.events[UP]);
if (delta) { return false;
_this.sliderY += -delta / 3; };
} })(this),
_this.scroll(); resize: (function(_this) {
return false; return function(e) {
} _this.reset();
};
})(this),
panedown: (function(_this) {
return function(e) {
_this.sliderY = (e.offsetY || e.originalEvent.layerY) - (_this.sliderHeight * 0.5);
_this.scroll();
_this.events.down(e);
return false;
};
})(this),
scroll: (function(_this) {
return function(e) {
_this.updateScrollValues();
if (_this.isBeingDragged) {
return;
}
if (!_this.iOSNativeScrolling) {
_this.sliderY = _this.sliderTop;
_this.setOnScrollStyles();
}
if (e == null) {
return;
}
if (_this.contentScrollTop >= _this.maxScrollTop) {
if (_this.options.preventPageScrolling) {
_this.preventScrolling(e, DOWN);
}
if (_this.prevScrollTop !== _this.maxScrollTop) {
_this.$el.trigger('scrollend');
}
} else if (_this.contentScrollTop === 0) {
if (_this.options.preventPageScrolling) {
_this.preventScrolling(e, UP);
}
if (_this.prevScrollTop !== 0) {
_this.$el.trigger('scrolltop');
}
}
};
})(this),
wheel: (function(_this) {
return function(e) {
var delta;
if (e == null) {
return;
}
delta = e.delta || e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail || (e.originalEvent && -e.originalEvent.detail);
if (delta) {
_this.sliderY += -delta / 3;
}
_this.scroll();
return false;
};
})(this)
}; };
}; };
/** /**
Adds event listeners with jQuery. Adds event listeners with jQuery.
@method addEvents @method addEvents
@private @private
*/ */
NanoScroll.prototype.addEvents = function() { NanoScroll.prototype.addEvents = function() {
var events; var events;
@ -557,12 +575,12 @@
this.$content.bind("" + SCROLL + " " + MOUSEWHEEL + " " + DOMSCROLL + " " + TOUCHMOVE, events[SCROLL]); this.$content.bind("" + SCROLL + " " + MOUSEWHEEL + " " + DOMSCROLL + " " + TOUCHMOVE, events[SCROLL]);
}; };
/** /**
Removes event listeners with jQuery. Removes event listeners with jQuery.
@method removeEvents @method removeEvents
@private @private
*/ */
NanoScroll.prototype.removeEvents = function() { NanoScroll.prototype.removeEvents = function() {
var events; var events;
@ -575,19 +593,19 @@
this.$content.unbind("" + SCROLL + " " + MOUSEWHEEL + " " + DOMSCROLL + " " + TOUCHMOVE, events[SCROLL]); this.$content.unbind("" + SCROLL + " " + MOUSEWHEEL + " " + DOMSCROLL + " " + TOUCHMOVE, events[SCROLL]);
}; };
/** /**
Generates nanoScroller's scrollbar and elements for it. Generates nanoScroller's scrollbar and elements for it.
@method generate @method generate
@chainable @chainable
@private @private
*/ */
NanoScroll.prototype.generate = function() { NanoScroll.prototype.generate = function() {
var contentClass, cssRule, currentPadding, options, paneClass, sliderClass; var contentClass, cssRule, currentPadding, options, pane, paneClass, sliderClass;
options = this.options; options = this.options;
paneClass = options.paneClass, sliderClass = options.sliderClass, contentClass = options.contentClass; paneClass = options.paneClass, sliderClass = options.sliderClass, contentClass = options.contentClass;
if (!this.$el.find("." + paneClass).length && !this.$el.find("." + sliderClass).length) { if (!(pane = this.$el.children("." + paneClass)).length && !pane.children("." + sliderClass).length) {
this.$el.append("<div class=\"" + paneClass + "\"><div class=\"" + sliderClass + "\" /></div>"); this.$el.append("<div class=\"" + paneClass + "\"><div class=\"" + sliderClass + "\" /></div>");
} }
this.pane = this.$el.children("." + paneClass); this.pane = this.$el.children("." + paneClass);
@ -610,11 +628,11 @@
return this; return this;
}; };
/** /**
@method restore @method restore
@private @private
*/ */
NanoScroll.prototype.restore = function() { NanoScroll.prototype.restore = function() {
this.stopped = false; this.stopped = false;
@ -624,17 +642,17 @@
this.addEvents(); this.addEvents();
}; };
/** /**
Resets nanoScroller's scrollbar. Resets nanoScroller's scrollbar.
@method reset @method reset
@chainable @chainable
@example @example
$(".nano").nanoScroller(); $(".nano").nanoScroller();
*/ */
NanoScroll.prototype.reset = function() { NanoScroll.prototype.reset = function() {
var content, contentHeight, contentStyle, contentStyleOverflowY, paneBottom, paneHeight, paneOuterHeight, paneTop, parentMaxHeight, sliderHeight; var content, contentHeight, contentPosition, contentStyle, contentStyleOverflowY, paneBottom, paneHeight, paneOuterHeight, paneTop, parentMaxHeight, right, sliderHeight;
if (this.iOSNativeScrolling) { if (this.iOSNativeScrolling) {
this.contentHeight = this.content.scrollHeight; this.contentHeight = this.content.scrollHeight;
return; return;
@ -693,16 +711,26 @@
opacity: (this.options.alwaysVisible ? 1 : ''), opacity: (this.options.alwaysVisible ? 1 : ''),
visibility: (this.options.alwaysVisible ? 'visible' : '') visibility: (this.options.alwaysVisible ? 'visible' : '')
}); });
contentPosition = this.$content.css('position');
if (contentPosition === 'static' || contentPosition === 'relative') {
right = parseInt(this.$content.css('right'), 10);
if (right) {
this.$content.css({
right: '',
marginRight: right
});
}
}
return this; return this;
}; };
/** /**
@method scroll @method scroll
@private @private
@example @example
$(".nano").nanoScroller({ scroll: 'top' }); $(".nano").nanoScroller({ scroll: 'top' });
*/ */
NanoScroll.prototype.scroll = function() { NanoScroll.prototype.scroll = function() {
if (!this.isActive) { if (!this.isActive) {
@ -718,6 +746,7 @@
return this; return this;
}; };
/** /**
Scroll at the bottom with an offset value Scroll at the bottom with an offset value
@method scrollBottom @method scrollBottom
@ -725,18 +754,18 @@
@chainable @chainable
@example @example
$(".nano").nanoScroller({ scrollBottom: value }); $(".nano").nanoScroller({ scrollBottom: value });
*/ */
NanoScroll.prototype.scrollBottom = function(offsetY) { NanoScroll.prototype.scrollBottom = function(offsetY) {
if (!this.isActive) { if (!this.isActive) {
return; return;
} }
this.reset();
this.$content.scrollTop(this.contentHeight - this.$content.height() - offsetY).trigger(MOUSEWHEEL); this.$content.scrollTop(this.contentHeight - this.$content.height() - offsetY).trigger(MOUSEWHEEL);
this.stop().restore();
return this; return this;
}; };
/** /**
Scroll at the top with an offset value Scroll at the top with an offset value
@method scrollTop @method scrollTop
@ -744,18 +773,18 @@
@chainable @chainable
@example @example
$(".nano").nanoScroller({ scrollTop: value }); $(".nano").nanoScroller({ scrollTop: value });
*/ */
NanoScroll.prototype.scrollTop = function(offsetY) { NanoScroll.prototype.scrollTop = function(offsetY) {
if (!this.isActive) { if (!this.isActive) {
return; return;
} }
this.reset();
this.$content.scrollTop(+offsetY).trigger(MOUSEWHEEL); this.$content.scrollTop(+offsetY).trigger(MOUSEWHEEL);
this.stop().restore();
return this; return this;
}; };
/** /**
Scroll to an element Scroll to an element
@method scrollTo @method scrollTo
@ -763,18 +792,17 @@
@chainable @chainable
@example @example
$(".nano").nanoScroller({ scrollTo: $('#a_node') }); $(".nano").nanoScroller({ scrollTo: $('#a_node') });
*/ */
NanoScroll.prototype.scrollTo = function(node) { NanoScroll.prototype.scrollTo = function(node) {
if (!this.isActive) { if (!this.isActive) {
return; return;
} }
this.reset(); this.scrollTop(this.$el.find(node).get(0).offsetTop);
this.scrollTop($(node).get(0).offsetTop);
return this; return this;
}; };
/** /**
To stop the operation. To stop the operation.
This option will tell the plugin to disable all event bindings and hide the gadget scrollbar from the UI. This option will tell the plugin to disable all event bindings and hide the gadget scrollbar from the UI.
@ -782,12 +810,12 @@
@chainable @chainable
@example @example
$(".nano").nanoScroller({ stop: true }); $(".nano").nanoScroller({ stop: true });
*/ */
NanoScroll.prototype.stop = function() { NanoScroll.prototype.stop = function() {
if (cAF) { if (cAF && this.scrollRAF) {
cAF(this.scrollRAF); cAF(this.scrollRAF);
this.scrollRAF = null;
} }
this.stopped = true; this.stopped = true;
this.removeEvents(); this.removeEvents();
@ -797,14 +825,14 @@
return this; return this;
}; };
/** /**
Destroys nanoScroller and restores browser's native scrollbar. Destroys nanoScroller and restores browser's native scrollbar.
@method destroy @method destroy
@chainable @chainable
@example @example
$(".nano").nanoScroller({ destroy: true }); $(".nano").nanoScroller({ destroy: true });
*/ */
NanoScroll.prototype.destroy = function() { NanoScroll.prototype.destroy = function() {
if (!this.stopped) { if (!this.stopped) {
@ -826,6 +854,7 @@
return this; return this;
}; };
/** /**
To flash the scrollbar gadget for an amount of time defined in plugin settings (defaults to 1,5s). To flash the scrollbar gadget for an amount of time defined in plugin settings (defaults to 1,5s).
Useful if you want to show the user (e.g. on pageload) that there is more content waiting for him. Useful if you want to show the user (e.g. on pageload) that there is more content waiting for him.
@ -833,11 +862,9 @@
@chainable @chainable
@example @example
$(".nano").nanoScroller({ flash: true }); $(".nano").nanoScroller({ flash: true });
*/ */
NanoScroll.prototype.flash = function() { NanoScroll.prototype.flash = function() {
var _this = this;
if (this.iOSNativeScrolling) { if (this.iOSNativeScrolling) {
return; return;
} }
@ -846,9 +873,11 @@
} }
this.reset(); this.reset();
this.pane.addClass('flashed'); this.pane.addClass('flashed');
setTimeout(function() { setTimeout((function(_this) {
_this.pane.removeClass('flashed'); return function() {
}, this.options.flashDelay); _this.pane.removeClass('flashed');
};
})(this), this.options.flashDelay);
return this; return this;
}; };