UNPKG

11.4 kBJavaScriptView Raw
1'use strict';
2
3exports.__esModule = true;
4exports.matches = exports.hasDOM = undefined;
5
6var _typeof2 = require('babel-runtime/helpers/typeof');
7
8var _typeof3 = _interopRequireDefault(_typeof2);
9
10exports.hasClass = hasClass;
11exports.addClass = addClass;
12exports.removeClass = removeClass;
13exports.toggleClass = toggleClass;
14exports.getNodeHozWhitespace = getNodeHozWhitespace;
15exports.getStyle = getStyle;
16exports.setStyle = setStyle;
17exports.scrollbar = scrollbar;
18exports.hasScroll = hasScroll;
19exports.getOffset = getOffset;
20exports.getPixels = getPixels;
21exports.getClosest = getClosest;
22exports.getMatches = getMatches;
23exports.saveRef = saveRef;
24
25var _string = require('./string');
26
27var _object = require('./object');
28
29function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
30
31/**
32 * 是否能使用 DOM 方法
33 * @type {Boolean}
34 */
35var hasDOM = exports.hasDOM = typeof window !== 'undefined' && !!window.document && !!document.createElement;
36
37/**
38 * 节点是否包含指定 className
39 * @param {Element} node
40 * @param {String} className
41 * @return {Boolean}
42 *
43 * @example
44 * dom.hasClass(document.body, 'foo');
45 */
46function hasClass(node, className) {
47 /* istanbul ignore if */
48 if (!hasDOM || !node) {
49 return false;
50 }
51
52 if (node.classList) {
53 return node.classList.contains(className);
54 } else {
55 return node.className.indexOf(className) > -1;
56 }
57}
58
59/**
60 * 添加 className
61 * @param {Element} node
62 * @param {String} className
63 *
64 * @example
65 * dom.addClass(document.body, 'foo');
66 */
67function addClass(node, className, _force) {
68 /* istanbul ignore if */
69 if (!hasDOM || !node) {
70 return;
71 }
72
73 if (node.classList) {
74 node.classList.add(className);
75 } else if (_force === true || !hasClass(node, className)) {
76 node.className += ' ' + className;
77 }
78}
79
80/**
81 * 移除 className
82 * @param {Element} node
83 * @param {String} className
84 *
85 * @example
86 * dom.removeClass(document.body, 'foo');
87 */
88function removeClass(node, className, _force) {
89 /* istanbul ignore if */
90 if (!hasDOM || !node) {
91 return;
92 }
93
94 if (node.classList) {
95 node.classList.remove(className);
96 } else if (_force === true || hasClass(node, className)) {
97 node.className = node.className.replace(className, '').replace(/\s+/g, ' ').trim();
98 }
99}
100
101/**
102 * 切换 className
103 * @param {Element} node
104 * @param {String} className
105 * @return {Boolean} 执行后节点上是否还有此 className
106 *
107 * @example
108 * dom.toggleClass(document.body, 'foo');
109 */
110function toggleClass(node, className) {
111 /* istanbul ignore if */
112 if (!hasDOM || !node) {
113 return false;
114 }
115
116 if (node.classList) {
117 return node.classList.toggle(className);
118 } else {
119 var flag = hasClass(node, className);
120 flag ? removeClass(node, className, true) : addClass(node, className, true);
121
122 return !flag;
123 }
124}
125
126/**
127 * 元素是否匹配 CSS 选择器
128 * @param {Element} node DOM 节点
129 * @param {String} selector CSS 选择器
130 * @return {Boolean}
131 *
132 * @example
133 * dom.matches(mountNode, '.container'); // boolean
134 */
135var matches = exports.matches = function () {
136 var matchesFn = null;
137 /* istanbul ignore else */
138 if (hasDOM) {
139 var _body = document.body || document.head;
140 matchesFn = _body.matches ? 'matches' : _body.webkitMatchesSelector ? 'webkitMatchesSelector' : _body.msMatchesSelector ? 'msMatchesSelector' : _body.mozMatchesSelector ? 'mozMatchesSelector' : null;
141 }
142
143 return function (node, selector) {
144 if (!hasDOM || !node) {
145 return false;
146 }
147
148 return matchesFn ? node[matchesFn](selector) : false;
149 };
150}();
151
152/**
153 * 获取元素计算后的样式
154 * @private
155 * @param {Element} node
156 * @return {Object}
157 */
158function _getComputedStyle(node) {
159 return node && node.nodeType === 1 ? window.getComputedStyle(node, null) : {};
160}
161
162var PIXEL_PATTERN = /margin|padding|width|height|max|min|offset|size|top/i;
163var removePixel = { left: 1, top: 1, right: 1, bottom: 1 };
164
165/**
166 * 校验并修正元素的样式属性值
167 * @private
168 * @param {Element} node
169 * @param {String} type
170 * @param {Number} value
171 */
172function _getStyleValue(node, type, value) {
173 type = type.toLowerCase();
174
175 if (value === 'auto') {
176 if (type === 'height') {
177 return node.offsetHeight || 0;
178 }
179 if (type === 'width') {
180 return node.offsetWidth || 0;
181 }
182 }
183
184 if (!(type in removePixel)) {
185 // 属性值是否需要去掉 px 单位,这里假定此类的属性值都是 px 为单位的
186 removePixel[type] = PIXEL_PATTERN.test(type);
187 }
188
189 return removePixel[type] ? parseFloat(value) || 0 : value;
190}
191
192var floatMap = { cssFloat: 1, styleFloat: 1, float: 1 };
193
194function getNodeHozWhitespace(node) {
195 var paddingLeft = getStyle(node, 'paddingLeft');
196 var paddingRight = getStyle(node, 'paddingRight');
197 var marginLeft = getStyle(node, 'marginLeft');
198 var marginRight = getStyle(node, 'marginRight');
199 return paddingLeft + paddingRight + marginLeft + marginRight;
200}
201
202/**
203 * 获取元素计算后的样式
204 * @param {Element} node DOM 节点
205 * @param {String} name 属性名
206 * @return {Number|Object}
207 */
208function getStyle(node, name) {
209 /* istanbul ignore if */
210 if (!hasDOM || !node) {
211 return null;
212 }
213
214 var style = _getComputedStyle(node);
215
216 // 如果不指定属性名,则返回全部值
217 if (arguments.length === 1) {
218 return style;
219 }
220
221 // if style is {}(e.g. node isn't a element node), return null
222 if ((0, _object.isPlainObject)(style)) {
223 return null;
224 }
225
226 name = floatMap[name] ? 'cssFloat' in node.style ? 'cssFloat' : 'styleFloat' : name;
227
228 return _getStyleValue(node, name, style.getPropertyValue((0, _string.hyphenate)(name)) || node.style[(0, _string.camelcase)(name)]);
229}
230
231/**
232 * 设置元素的样式
233 * @param {Element} node DOM 节点
234 * @param {Object|String} name 属性名,或者是一个对象,包含多个属性
235 * @param {Number|String} value 属性值
236 *
237 * @example
238 * // 设置单个属性值
239 * dom.setStyle(mountNode, 'width', 100);
240 * // 设置多条属性值
241 * dom.setStyle(mountNode, {
242 * width: 100,
243 * height: 200
244 * });
245 */
246function setStyle(node, name, value) {
247 /* istanbul ignore if */
248 if (!hasDOM || !node) {
249 return false;
250 }
251
252 // 批量设置多个值
253 if ((typeof name === 'undefined' ? 'undefined' : (0, _typeof3.default)(name)) === 'object' && arguments.length === 2) {
254 (0, _object.each)(name, function (val, key) {
255 return setStyle(node, key, val);
256 });
257 } else {
258 name = floatMap[name] ? 'cssFloat' in node.style ? 'cssFloat' : 'styleFloat' : name;
259 if (typeof value === 'number' && PIXEL_PATTERN.test(name)) {
260 value = value + 'px';
261 }
262 node.style[(0, _string.camelcase)(name)] = value; // IE8 support
263 }
264}
265
266var isScrollDisplay = function isScrollDisplay(element) {
267 try {
268 var scrollbarStyle = window.getComputedStyle(element, '::-webkit-scrollbar');
269 return !scrollbarStyle || scrollbarStyle.getPropertyValue('display') !== 'none';
270 } catch (e) {
271 // ignore error for firefox
272 }
273
274 return true;
275};
276
277/**
278 * 获取默认的滚动条大小(通过创造一个滚动元素,读取滚动元素的滚动条信息)
279 * @return {Object} width, height
280 */
281function scrollbar() {
282 var scrollDiv = document.createElement('div');
283 scrollDiv.className += 'just-to-get-scrollbar-size';
284
285 setStyle(scrollDiv, {
286 position: 'absolute',
287 width: '100px',
288 height: '100px',
289 overflow: 'scroll',
290 top: '-9999px'
291 });
292 document.body && document.body.appendChild(scrollDiv);
293 var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
294 var scrollbarHeight = scrollDiv.offsetHeight - scrollDiv.clientHeight;
295 document.body.removeChild(scrollDiv);
296
297 return {
298 width: scrollbarWidth,
299 height: scrollbarHeight
300 };
301}
302
303function hasScroll(containerNode) {
304 // 当元素带有 overflow: hidden 一定没有滚动条
305 var overflow = getStyle(containerNode, 'overflow');
306 if (overflow === 'hidden') {
307 return false;
308 }
309
310 var parentNode = containerNode.parentNode;
311
312 return parentNode && parentNode.scrollHeight > parentNode.clientHeight && scrollbar().width > 0 && isScrollDisplay(parentNode) && isScrollDisplay(containerNode);
313}
314
315/**
316 * 获取元素距离视口顶部和左边的偏移距离
317 * @return {Object} top, left
318 */
319function getOffset(node) {
320 var rect = node.getBoundingClientRect();
321 var win = node.ownerDocument.defaultView;
322 return {
323 top: rect.top + win.pageYOffset,
324 left: rect.left + win.pageXOffset
325 };
326}
327
328/**
329 * 获取不同单位转为 number 的长度
330 * @param {string|number} len 传入的长度
331 * @return {number} pixels
332 */
333function getPixels(len) {
334 var win = document.defaultView;
335 if (typeof +len === 'number' && !isNaN(+len)) {
336 return +len;
337 }
338
339 if (typeof len === 'string') {
340 var PX_REG = /(\d+)px/;
341 var VH_REG = /(\d+)vh/;
342 if (Array.isArray(len.match(PX_REG))) {
343 return +len.match(PX_REG)[1] || 0;
344 }
345
346 if (Array.isArray(len.match(VH_REG))) {
347 var _1vh = win.innerHeight / 100;
348 return +(len.match(VH_REG)[1] * _1vh) || 0;
349 }
350 }
351
352 return 0;
353}
354
355/**
356 * 匹配特定选择器且离当前元素最近的祖先元素(也可以是当前元素本身),如果匹配不到,则返回 null
357 * @param {element} dom 待匹配的元素
358 * @param {string} selecotr 选择器
359 * @return {element} parent
360 */
361function getClosest(dom, selector) {
362 /* istanbul ignore if */
363 if (!hasDOM || !dom) {
364 return null;
365 }
366
367 // ie9
368 /* istanbul ignore if */
369 if (!Element.prototype.closest) {
370 if (!document.documentElement.contains(dom)) return null;
371 do {
372 if (getMatches(dom, selector)) return dom;
373 dom = dom.parentElement;
374 } while (dom !== null);
375 } else {
376 return dom.closest(selector);
377 }
378 return null;
379}
380
381/**
382 * 如果元素被指定的选择器字符串选择,getMatches() 方法返回true; 否则返回false
383 * @param {element} dom 待匹配的元素
384 * @param {string} selecotr 选择器
385 * @return {element} parent
386 */
387function getMatches(dom, selector) {
388 /* istanbul ignore if */
389 if (!hasDOM || !dom) {
390 return null;
391 }
392
393 /* istanbul ignore if */
394 if (Element.prototype.matches) {
395 return dom.matches(selector);
396 } else if (Element.prototype.msMatchesSelector) {
397 return dom.msMatchesSelector(selector);
398 } else if (Element.prototype.webkitMatchesSelector) {
399 return dom.webkitMatchesSelector(selector);
400 }
401
402 return null;
403}
404
405function saveRef(ref) {
406 if (!ref) {
407 return null;
408 }
409 return function (element) {
410 if (typeof ref === 'string') {
411 throw new Error('can not set ref string for ' + ref);
412 } else if (typeof ref === 'function') {
413 ref(element);
414 } else if (Object.prototype.hasOwnProperty.call(ref, 'current')) {
415 ref.current = element;
416 }
417 };
418}
\No newline at end of file