-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdouble-scroll-bars.js
More file actions
165 lines (133 loc) · 5.57 KB
/
double-scroll-bars.js
File metadata and controls
165 lines (133 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
* AngularJS directives for double scroll bars
* Author: przno
* Homepage: https://github.com/przno/double-scroll-bars
* License: MIT
*/
(function(angular) {
'use strict';
angular.module('doubleScrollBars', [])
.directive('doubleScrollBarHorizontal', ['$timeout', '$dsb', '$$dsbStorage',
function($timeout, $dsb, $$dsbStorage) {
return {
// usage:
// <div data-double-scroll-bar-horizontal> {{content}} or static content </div>
// or with input value:
// <div data-double-scroll-bar-horizontal="always"> {{content}} or static content </div>
restrict: 'A',
// transclude the content
transclude: true,
// isolate scope
scope: {
doubleScrollBarHorizontal: '@', // if equals 'always' will display inactive scroll bars even if there is nothing to scroll; otherwise show them only when content overflows display area
id: '@' // optional id
},
// HTML template
template: '' +
'<div>' +
' <div style="overflow-y:hidden;" data-ng-style="{height:nativeScrollBarHeight}">' +
' <div style="overflow-y:hidden;position:relative;top:-1px;" data-ng-style="{\'overflow-x\':doubleScrollBarHorizontal==\'always\'?\'scroll\':\'auto\',height:scrollBarElementHeight}">' +
' <div data-ng-style="{width:wrapper2scrollWidth,height:scrollBarElementHeight}"></div>' +
' </div>' +
' </div>' +
' <div data-ng-style="{\'overflow-x\':doubleScrollBarHorizontal==\'always\'?\'scroll\':\'auto\'}">' +
' <div data-ng-transclude></div>' +
' </div>' +
'</div>',
// link function with the logic
link: function($scope, iElm, iAttrs, controller) {
var barSize = $dsb.getSize();
$scope.nativeScrollBarHeight = barSize + 'px';
$scope.scrollBarElementHeight = parseInt(barSize + 1) + 'px';
// scroll width of the wrapper2 div, width of div inside wrapper1 will be set to the same value
$scope.wrapper2scrollWidth = '0px';
// angular.element representation od the root <div> of this directive
var rootDiv = iElm.children().eq(0);
// angular.element object for the first div in the root // <div style="overflow-y: hidden;" data-ng-style="{\'overflow-x\': doubleScrollBarHorizontal == \'always\' ? \'scroll\' : \'auto\', height: nativeScrollBarHeight}">
// the 'virtual' top scroll bar will be here
var wrapper1 = rootDiv.children().eq(0).children().eq(0);
// angular.element object for the second div in the root // <div data-ng-style="{\'overflow-x\': doubleScrollBarHorizontal == \'always\' ? \'scroll\' : \'auto\'}">
// the 'real' bottom scroll bar will be here
var wrapper2 = rootDiv.children().eq(1);
// get native DOM element from angular.element
var wrapper1dom = wrapper1[0];
var wrapper2dom = wrapper2[0];
// if scrolling one ruler, scroll also the other one
wrapper1.on('scroll', function() {
wrapper2dom.scrollLeft = wrapper1dom.scrollLeft;
});
wrapper2.on('scroll', function() {
wrapper1dom.scrollLeft = wrapper2dom.scrollLeft;
});
var firstTime = true;
// watch for a change of the width (e.g. transcluded content changed and so changed its width)
$scope.$watch(function() {
return ($scope.wrapper2scrollWidth = wrapper2dom.scrollWidth + 'px');
}, function(newValue, oldValue) {
// run $apply one more time so the scroll bars are in sync
// $timeout to run it on next $digest cycle, otherwise angular will complain of '$digest already in process'
$timeout(function() {
$scope.$apply();
// first time after recompiled and width set (width set in $apply())
if (firstTime) {
// initial values for scroll position - zero if this directive is compiled very first time or if no id provided, otherwise use last scroll position (from service)
wrapper1dom.scrollLeft = $$dsbStorage.get($scope.id) || 0;
wrapper2dom.scrollLeft = $$dsbStorage.get($scope.id) || 0;
firstTime = false;
}
});
});
// save the scroll position for future (if id was specified)
$scope.$on('$destroy', function() {
if ($scope.id !== undefined)
$$dsbStorage.set($scope.id, wrapper1dom.scrollLeft);
});
}
};
}
])
// keeps the last scroll position for a directive specified by its id (in case the directive has been recompiled)
.service('$$dsbStorage', function() {
var storage = {};
return {
get: function(id) {
return storage[id];
},
set: function(id, value) {
storage[id] = value;
}
};
})
// calculate scroll bar's width/height (in pixels) as it differs between various browsers and systems
// e.g. FF on Win7 17px, FF on Ubuntu 15px, FF on Xubuntu 13px
.service('$dsb', function() {
function getWidth() {
// dang! DOM manipulation outside Angular's directives. But the element only lives for a glympse of time, I remove it immediatelly...
var inner = document.createElement('div');
var outer = document.createElement('div');
inner.style.width = '100%';
inner.style.height = '200px';
outer.style.width = '200px';
outer.style.height = '150px';
outer.style.position = 'absolute';
outer.style.top = '0';
outer.style.left = '0';
outer.style.visibility = 'hidden';
outer.style.overflow = 'hidden';
outer.appendChild(inner);
document.body.appendChild(outer);
var width1 = inner.offsetWidth;
outer.style.overflow = 'scroll';
var width2 = outer.clientWidth;
document.body.removeChild(outer);
return (width1 - width2);
}
var width;
return {
getSize: function() {
width = width || getWidth();
return width;
}
};
});
})(angular);