2020-03-31 13:47:59 +08:00

368 lines
8.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { camelcase, hyphenate } from './string';
import { each } from './object';
/**
* 是否能使用 DOM 方法
* @type {Boolean}
*/
export const hasDOM =
typeof window !== 'undefined' &&
!!window.document &&
!!document.createElement;
/**
* 节点是否包含指定 className
* @param {Element} node
* @param {String} className
* @return {Boolean}
*
* @example
* dom.hasClass(document.body, 'foo');
*/
export function hasClass(node, className) {
/* istanbul ignore if */
if (!hasDOM || !node) {
return false;
}
if (node.classList) {
return node.classList.contains(className);
} else {
return node.className.indexOf(className) > -1;
}
}
/**
* 添加 className
* @param {Element} node
* @param {String} className
*
* @example
* dom.addClass(document.body, 'foo');
*/
export function addClass(node, className, _force) {
/* istanbul ignore if */
if (!hasDOM || !node) {
return;
}
if (node.classList) {
node.classList.add(className);
} else if (_force === true || !hasClass(node, className)) {
node.className += ` ${className}`;
}
}
/**
* 移除 className
* @param {Element} node
* @param {String} className
*
* @example
* dom.removeClass(document.body, 'foo');
*/
export function removeClass(node, className, _force) {
/* istanbul ignore if */
if (!hasDOM || !node) {
return;
}
if (node.classList) {
node.classList.remove(className);
} else if (_force === true || hasClass(node, className)) {
node.className = node.className
.replace(className, '')
.replace(/\s+/g, ' ')
.trim();
}
}
/**
* 切换 className
* @param {Element} node
* @param {String} className
* @return {Boolean} 执行后节点上是否还有此 className
*
* @example
* dom.toggleClass(document.body, 'foo');
*/
export function toggleClass(node, className) {
/* istanbul ignore if */
if (!hasDOM || !node) {
return false;
}
if (node.classList) {
return node.classList.toggle(className);
} else {
const flag = hasClass(node, className);
flag
? removeClass(node, className, true)
: addClass(node, className, true);
return !flag;
}
}
/**
* 元素是否匹配 CSS 选择器
* @param {Element} node DOM 节点
* @param {String} selector CSS 选择器
* @return {Boolean}
*
* @example
* dom.matches(mountNode, '.container'); // boolean
*/
export const matches = (function() {
let matchesFn = null;
/* istanbul ignore else */
if (hasDOM) {
const _body = document.body || document.head;
matchesFn = _body.matches
? 'matches'
: _body.webkitMatchesSelector
? 'webkitMatchesSelector'
: _body.msMatchesSelector
? 'msMatchesSelector'
: _body.mozMatchesSelector
? 'mozMatchesSelector'
: null;
}
return function(node, selector) {
if (!hasDOM || !node) {
return false;
}
return matchesFn ? node[matchesFn](selector) : false;
};
})();
/**
* 获取元素计算后的样式
* @private
* @param {Element} node
* @return {Object}
*/
function _getComputedStyle(node) {
return node && node.nodeType === 1
? window.getComputedStyle(node, null)
: {};
}
const PIXEL_PATTERN = /margin|padding|width|height|max|min|offset|size/i;
const removePixel = { left: 1, top: 1, right: 1, bottom: 1 };
/**
* 校验并修正元素的样式属性值
* @private
* @param {Element} node
* @param {String} type
* @param {Number} value
*/
function _getStyleValue(node, type, value) {
type = type.toLowerCase();
if (value === 'auto') {
if (type === 'height') {
return node.offsetHeight || 0;
}
if (type === 'width') {
return node.offsetWidth || 0;
}
}
if (!(type in removePixel)) {
// 属性值是否需要去掉 px 单位,这里假定此类的属性值都是 px 为单位的
removePixel[type] = PIXEL_PATTERN.test(type);
}
return removePixel[type] ? parseFloat(value) || 0 : value;
}
const floatMap = { cssFloat: 1, styleFloat: 1, float: 1 };
/**
* 获取元素计算后的样式
* @param {Element} node DOM 节点
* @param {String} name 属性名
* @return {Number|Object}
*/
export function getStyle(node, name) {
/* istanbul ignore if */
if (!hasDOM || !node) {
return null;
}
const style = _getComputedStyle(node);
// 如果不指定属性名,则返回全部值
if (arguments.length === 1) {
return style;
}
name = floatMap[name]
? 'cssFloat' in node.style
? 'cssFloat'
: 'styleFloat'
: name;
return _getStyleValue(
node,
name,
style.getPropertyValue(hyphenate(name)) || node.style[camelcase(name)]
);
}
/**
* 设置元素的样式
* @param {Element} node DOM 节点
* @param {Object|String} name 属性名,或者是一个对象,包含多个属性
* @param {Number|String} value 属性值
*
* @example
* // 设置单个属性值
* dom.setStyle(mountNode, 'width', 100);
* // 设置多条属性值
* dom.setStyle(mountNode, {
* width: 100,
* height: 200
* });
*/
export function setStyle(node, name, value) {
/* istanbul ignore if */
if (!hasDOM || !node) {
return false;
}
// 批量设置多个值
if (typeof name === 'object' && arguments.length === 2) {
each(name, (val, key) => setStyle(node, key, val));
} else {
name = floatMap[name]
? 'cssFloat' in node.style
? 'cssFloat'
: 'styleFloat'
: name;
if (typeof value === 'number' && PIXEL_PATTERN.test(name)) {
value = `${value}px`;
}
node.style[camelcase(name)] = value; // IE8 support
}
}
/**
* 获取默认的滚动条大小
* @return {Object} width, height
*/
export function scrollbar() {
const scrollDiv = document.createElement('div');
setStyle(scrollDiv, {
position: 'absolute',
width: '100px',
height: '100px',
overflow: 'scroll',
top: '-9999px',
});
document.body.appendChild(scrollDiv);
const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
const scrollbarHeight = scrollDiv.offsetHeight - scrollDiv.clientHeight;
document.body.removeChild(scrollDiv);
return {
width: scrollbarWidth,
height: scrollbarHeight,
};
}
/**
* 获取元素距离视口顶部和左边的偏移距离
* @return {Object} top, left
*/
export function getOffset(node) {
const rect = node.getBoundingClientRect();
const win = node.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset,
};
}
/**
* 获取不同单位转为 number 的长度
* @param {string|number} len 传入的长度
* @return {number} pixels
*/
export function getPixels(len) {
const win = document.defaultView;
if (typeof +len === 'number' && !isNaN(+len)) {
return +len;
}
if (typeof len === 'string') {
const PX_REG = /(\d+)px/;
const VH_REG = /(\d+)vh/;
if (Array.isArray(len.match(PX_REG))) {
return +len.match(PX_REG)[1] || 0;
}
if (Array.isArray(len.match(VH_REG))) {
const _1vh = win.innerHeight / 100;
return +(len.match(VH_REG)[1] * _1vh) || 0;
}
}
return 0;
}
/**
* 匹配特定选择器且离当前元素最近的祖先元素(也可以是当前元素本身),如果匹配不到,则返回 null
* @param {element} dom 待匹配的元素
* @param {string} selecotr 选择器
* @return {element} parent
*/
export function getClosest(dom, selector) {
/* istanbul ignore if */
if (!hasDOM || !dom) {
return null;
}
// ie9
/* istanbul ignore if */
if (!Element.prototype.closest) {
if (!document.documentElement.contains(dom)) return null;
do {
if (getMatches(dom, selector)) return dom;
dom = dom.parentElement;
} while (dom !== null);
} else {
return dom.closest(selector);
}
return null;
}
/**
* 如果元素被指定的选择器字符串选择getMatches() 方法返回true; 否则返回false
* @param {element} dom 待匹配的元素
* @param {string} selecotr 选择器
* @return {element} parent
*/
export function getMatches(dom, selector) {
/* istanbul ignore if */
if (!hasDOM || !dom) {
return null;
}
/* istanbul ignore if */
if (Element.prototype.matches) {
return dom.matches(selector);
} else if (Element.prototype.msMatchesSelector) {
return dom.msMatchesSelector(selector);
} else if (Element.prototype.webkitMatchesSelector) {
return dom.webkitMatchesSelector(selector);
}
return null;
}