源码分析之Leaflet中dom模块DomUtil的实现原理

概述

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,
  };
}

主要工具函数介绍

  1. TRANSFORMTRANSITION
  • 作用:检测浏览器支持的 CSS 变换(transform)和过渡(transition)属性
  • 实现:通过testProp函数遍历一组可能的属性名称,返回浏览器支持的第一个属性
  • 意义:跨浏览器兼容性处理,确保在不同浏览器中正确应用 CSS 变换和过渡
  1. TRANSITION_END
  • ​ 作用:确定浏览器支持的 transitionend 事件名称。
  • 实现:根据 TRANSITION 属性的值,返回对应的事件名称(如 webkitTransitionEndtransitionend)。
  • ​ 意义:确保在 CSS 过渡结束时正确触发事件
  1. DOM 操作工具函数
  • get(id)

    • 作用:通过 ID 获取 DOM 元素。
    • 实现:如果传入的是字符串,调用 document.getElementById;否则直接返回传入的值。
  • getStyle(el, style)

    • ​ 作用:获取元素的样式值。
    • 实现:优先从 el.styleel.currentStyle 中获取样式值,如果未找到或值为 auto,则通过 getComputedStyle 获取。
  • create(tagName, className, container)

    • 作用:创建并返回一个 DOM 元素。
    • 实现:使用 document.createElement 创建元素,并可选地将其附加到指定容器中。
  • remove(el)

    • 作用:从 DOM 中移除元素。
    • 实现:调用 parentNode.removeChild
  • empty(el)

    • 作用:清空元素的所有子节点。
    • 实现:循环移除 el.firstChild
  • toFront(el)toBack(el)

    • ​ 作用:将元素移动到其父容器的顶部或底部。
    • 实现:通过 appendChildinsertBefore 调整元素的位置。

​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 滤镜实现。
  1. 变换和位置操作工具函数
  • testProp(props)

    • 作用:检测浏览器支持的 CSS 属性。
    • 实现:遍历传入的属性列表,返回第一个支持的属性。
  • setTransform(el, offset, scale)

    • 作用:设置元素的变换(平移和缩放)。
    • 实现:根据浏览器支持,使用 translatetranslate3d
  • setPosition(el, point)getPosition(el)

    • 作用:设置或获取元素的位置。
    • 实现:优先使用 transform,否则通过 lefttop 实现。


7. 文本选择和图像拖拽工具函数

  • disableTextSelection()enableTextSelection()

    • 作用:禁用或启用文本选择。
    • 实现:通过 selectstart 事件或 userSelect 属性实现。
  • disableImageDrag()enableImageDrag()

    • 作用:禁用或启用图像拖拽。
    • 实现:通过 dragstart 事件实现。
  1. 轮廓线操作工具函数
  • preventOutline(element)restoreOutline()
    • 作用:禁用或恢复元素的轮廓线。
    • 实现:通过修改 outlineStyle 实现。
  1. 其他工具函数
  • getSizedParentNode(element)

    • 作用:获取具有实际尺寸的父节点。
    • 实现:循环查找父节点,直到找到具有 offsetWidthoffsetHeight 的节点。
  • getScale(element)

    • 作用:获取元素的缩放比例。
    • 实现:通过 getBoundingClientRectoffsetWidth/Height 计算。

总结

这段代码是 Leaflet 中用于处理 DOM 操作和事件的工具函数集合,涵盖了元素创建、删除、样式操作、变换、事件处理等功能。它的设计目标是:

  1. 跨浏览器兼容性:通过检测和适配不同浏览器的属性和事件名称。
  2. 轻量高效:使用原生 DOM API,避免不必要的依赖。
  3. 模块化:每个函数功能独立,易于复用和扩展。

这些工具函数为 Leaflet 的核心功能(如地图渲染、交互、动画等)提供了基础支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinuss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值