概述
DomUtil
模块是Leaflet中用于处理 DOM 操作和事件的核心工具函数集合,这些工具函数主要用于处理 DOM 元素的样式、位置、变换、事件等操作。
源码分析
源码实现如下
DomUtil
源码实现如下:
export var TRANSFORM = testProp([
"transform",
"webkitTransform",
"OTransform",
"MozTransform",
"msTransform",
]);
export var TRANSITION = testProp([
"webkitTransition",
"transition",
"OTransition",
"MozTransition",
"msTransition",
]);
export var TRANSITION_END =
TRANSITION === "webkitTransition" || TRANSITION === "OTransition"
? TRANSITION + "End"
: "transitionend";
export function get(id) {
return typeof id === "string" ? document.getElementById(id) : id;
}
export function getStyle(el, style) {
var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
if ((!value || value === "auto") && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
return value === "auto" ? null : value;
}
export function create(tagName, className, container) {
var el = document.createElement(tagName);
el.className = className || "";
if (container) {
container.appendChild(el);
}
return el;
}
export function remove(el) {
var parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
}
export function empty(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
export function toFront(el) {
var parent = el.parentNode;
if (parent && parent.lastChild !== el) {
parent.appendChild(el);
}
}
export function toBack(el) {
var parent = el.parentNode;
if (parent && parent.firstChild !== el) {
parent.insertBefore(el, parent.firstChild);
}
}
export function hasClass(el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = getClass(el);
return (
className.length > 0 &&
new RegExp("(^|\\s)" + name + "(\\s|$)").test(className)
);
}
export function addClass(el, name) {
if (el.classList !== undefined) {
var classes = Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!hasClass(el, name)) {
var className = getClass(el);
setClass(el, (className ? className + " " : "") + name);
}
}
export function removeClass(el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
setClass(
el,
Util.trim((" " + getClass(el) + " ").replace(" " + name + " ", " "))
);
}
}
export function setClass(el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
}
export function getClass(el) {
if (el.correspondingElement) {
el = el.correspondingElement;
}
return el.className.baseVal === undefined
? el.className
: el.className.baseVal;
}
export function setOpacity(el, value) {
if ("opacity" in el.style) {
el.style.opacity = value;
} else if ("filter" in el.style) {
_setOpacityIE(el, value);
}
}
function _setOpacityIE(el, value) {
var filter = false,
filterName = "DXImageTransform.Microsoft.Alpha";
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
if (value === 1) {
return;
}
}
value = Math.round(value * 100);
if (filter) {
filter.Enabled = value !== 100;
filter.Opacity = value;
} else {
el.style.filter += " progid:" + filterName + "(opacity=" + value + ")";
}
}
export function testProp(props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
}
export function setTransform(el, offset, scale) {
var pos = offset || new Point(0, 0);
el.style[TRANSFORM] =
(Browser.ie3d
? "translate(" + pos.x + "px," + pos.y + "px)"
: "translate3d(" + pos.x + "px," + pos.y + "px,0)") +
(scale ? " scale(" + scale + ")" : "");
}
export function setPosition(el, point) {
el._leaflet_pos = point;
if (Browser.any3d) {
setTransform(el, point);
} else {
el.style.left = point.x + "px";
el.style.top = point.y + "px";
}
}
export function getPosition(el) {
return el._leaflet_pos || new Point(0, 0);
}
export var disableTextSelection;
export var enableTextSelection;
var _userSelect;
if ("onselectstart" in document) {
disableTextSelection = function () {
DomEvent.on(window, "selectstart", DomEvent.preventDefault);
};
enableTextSelection = function () {
DomEvent.off(window, "selectstart", DomEvent.preventDefault);
};
} else {
var userSelectProperty = testProp([
"userSelect",
"WebkitUserSelect",
"OUserSelect",
"MozUserSelect",
"msUserSelect",
]);
disableTextSelection = function () {
if (userSelectProperty) {
var style = document.documentElement.style;
_userSelect = style[userSelectProperty];
style[userSelectProperty] = "none";
}
};
enableTextSelection = function () {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = _userSelect;
_userSelect = undefined;
}
};
}
export function disableImageDrag() {
DomEvent.on(window, "dragstart", DomEvent.preventDefault);
}
export function enableImageDrag() {
DomEvent.off(window, "dragstart", DomEvent.preventDefault);
}
var _outlineElement, _outlineStyle;
export function preventOutline(element) {
while (element.tabIndex === -1) {
element = element.parentNode;
}
if (!element.style) {
return;
}
restoreOutline();
_outlineElement = element;
_outlineStyle = element.style.outlineStyle;
element.style.outlineStyle = "none";
DomEvent.on(window, "keydown", restoreOutline);
}
export function restoreOutline() {
if (!_outlineElement) {
return;
}
_outlineElement.style.outlineStyle = _outlineStyle;
_outlineElement = undefined;
_outlineStyle = undefined;
DomEvent.off(window, "keydown", restoreOutline);
}
export function getSizedParentNode(element) {
do {
element = element.parentNode;
} while (
(!element.offsetWidth || !element.offsetHeight) &&
element !== document.body
);
return element;
}
export function getScale(element) {
var rect = element.getBoundingClientRect(); // Read-only in old browsers.
return {
x: rect.width / element.offsetWidth || 1,
y: rect.height / element.offsetHeight || 1,
boundingClientRect: rect,
};
}
主要工具函数介绍
TRANSFORM
和TRANSITION
- 作用:检测浏览器支持的 CSS 变换(
transform
)和过渡(transition
)属性 - 实现:通过
testProp
函数遍历一组可能的属性名称,返回浏览器支持的第一个属性 - 意义:跨浏览器兼容性处理,确保在不同浏览器中正确应用 CSS 变换和过渡
TRANSITION_END
- 作用:确定浏览器支持的
transitionend
事件名称。 - 实现:根据
TRANSITION
属性的值,返回对应的事件名称(如webkitTransitionEnd
或transitionend
)。 - 意义:确保在 CSS 过渡结束时正确触发事件
- DOM 操作工具函数
-
get(id)
- 作用:通过
ID
获取 DOM 元素。 - 实现:如果传入的是字符串,调用
document.getElementById
;否则直接返回传入的值。
- 作用:通过
-
getStyle(el, style)
- 作用:获取元素的样式值。
- 实现:优先从
el.style
或el.currentStyle
中获取样式值,如果未找到或值为auto
,则通过getComputedStyle
获取。
-
create(tagName, className, container)
- 作用:创建并返回一个 DOM 元素。
- 实现:使用
document.createElement
创建元素,并可选地将其附加到指定容器中。
-
remove(el)
- 作用:从 DOM 中移除元素。
- 实现:调用
parentNode.removeChild
。
-
empty(el)
- 作用:清空元素的所有子节点。
- 实现:循环移除
el.firstChild
。
-
toFront(el)
和toBack(el)
- 作用:将元素移动到其父容器的顶部或底部。
- 实现:通过
appendChild
或insertBefore
调整元素的位置。
4. 类名操作工具函数
-
hasClass(el, name)
- 作用:检查元素是否包含指定的类名。
- 实现:优先使用
el.classList.contains
,否则通过正则表达式匹配。
-
addClass(el, name)
和removeClass(el, name)
- 作用:添加或移除元素的类名。
- 实现:优先使用
el.classList
,否则通过字符串操作修改className
。
-
setClass(el, name)
和getClass(el)
- 作用:设置或获取元素的类名。
- 实现:处理普通元素和 SVG 元素的兼容性。
5. 透明度操作工具函数
-
setOpacity(el, value)
- 作用:设置元素的透明度。
- 实现:优先使用
opacity
属性,否则通过 IE 的filter
属性实现。
-
setOpacityIE(el, value)
- 作用:在 IE 中设置透明度。
- 实现:通过
DXImageTransform.Microsoft.Alpha
滤镜实现。
- 变换和位置操作工具函数
-
testProp(props)
- 作用:检测浏览器支持的 CSS 属性。
- 实现:遍历传入的属性列表,返回第一个支持的属性。
-
setTransform(el, offset, scale)
- 作用:设置元素的变换(平移和缩放)。
- 实现:根据浏览器支持,使用
translate
或translate3d
。
-
setPosition(el, point)
和getPosition(el)
- 作用:设置或获取元素的位置。
- 实现:优先使用
transform
,否则通过left
和top
实现。
7. 文本选择和图像拖拽工具函数
-
disableTextSelection()
和enableTextSelection()
- 作用:禁用或启用文本选择。
- 实现:通过
selectstart
事件或userSelect
属性实现。
-
disableImageDrag()
和enableImageDrag()
- 作用:禁用或启用图像拖拽。
- 实现:通过
dragstart
事件实现。
- 轮廓线操作工具函数
preventOutline(element)
和restoreOutline()
- 作用:禁用或恢复元素的轮廓线。
- 实现:通过修改
outlineStyle
实现。
- 其他工具函数
-
getSizedParentNode(element)
- 作用:获取具有实际尺寸的父节点。
- 实现:循环查找父节点,直到找到具有
offsetWidth
和offsetHeight
的节点。
-
getScale(element)
- 作用:获取元素的缩放比例。
- 实现:通过
getBoundingClientRect
和offsetWidth/Height
计算。
总结
这段代码是 Leaflet 中用于处理 DOM 操作和事件的工具函数集合,涵盖了元素创建、删除、样式操作、变换、事件处理等功能。它的设计目标是:
- 跨浏览器兼容性:通过检测和适配不同浏览器的属性和事件名称。
- 轻量高效:使用原生 DOM API,避免不必要的依赖。
- 模块化:每个函数功能独立,易于复用和扩展。
这些工具函数为 Leaflet 的核心功能(如地图渲染、交互、动画等)提供了基础支持。