UNPKG

12.1 kBJavaScriptView Raw
1import * as DomEvent from './DomEvent';
2import * as Util from '../core/Util';
3import {Point} from '../geometry/Point';
4import Browser from '../core/Browser';
5
6/*
7 * @namespace DomUtil
8 *
9 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
10 * tree, used by Leaflet internally.
11 *
12 * Most functions expecting or returning a `HTMLElement` also work for
13 * SVG elements. The only difference is that classes refer to CSS classes
14 * in HTML and SVG classes in SVG.
15 */
16
17
18// @property TRANSFORM: String
19// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
20export var TRANSFORM = testProp(
21 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
22
23// webkitTransition comes first because some browser versions that drop vendor prefix don't do
24// the same for the transitionend event, in particular the Android 4.1 stock browser
25
26// @property TRANSITION: String
27// Vendor-prefixed transition style name.
28export var TRANSITION = testProp(
29 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
30
31// @property TRANSITION_END: String
32// Vendor-prefixed transitionend event name.
33export var TRANSITION_END =
34 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
35
36
37// @function get(id: String|HTMLElement): HTMLElement
38// Returns an element given its DOM id, or returns the element itself
39// if it was passed directly.
40export function get(id) {
41 return typeof id === 'string' ? document.getElementById(id) : id;
42}
43
44// @function getStyle(el: HTMLElement, styleAttrib: String): String
45// Returns the value for a certain style attribute on an element,
46// including computed values or values set through CSS.
47export function getStyle(el, style) {
48 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
49
50 if ((!value || value === 'auto') && document.defaultView) {
51 var css = document.defaultView.getComputedStyle(el, null);
52 value = css ? css[style] : null;
53 }
54 return value === 'auto' ? null : value;
55}
56
57// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
58// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
59export function create(tagName, className, container) {
60 var el = document.createElement(tagName);
61 el.className = className || '';
62
63 if (container) {
64 container.appendChild(el);
65 }
66 return el;
67}
68
69// @function remove(el: HTMLElement)
70// Removes `el` from its parent element
71export function remove(el) {
72 var parent = el.parentNode;
73 if (parent) {
74 parent.removeChild(el);
75 }
76}
77
78// @function empty(el: HTMLElement)
79// Removes all of `el`'s children elements from `el`
80export function empty(el) {
81 while (el.firstChild) {
82 el.removeChild(el.firstChild);
83 }
84}
85
86// @function toFront(el: HTMLElement)
87// Makes `el` the last child of its parent, so it renders in front of the other children.
88export function toFront(el) {
89 var parent = el.parentNode;
90 if (parent && parent.lastChild !== el) {
91 parent.appendChild(el);
92 }
93}
94
95// @function toBack(el: HTMLElement)
96// Makes `el` the first child of its parent, so it renders behind the other children.
97export function toBack(el) {
98 var parent = el.parentNode;
99 if (parent && parent.firstChild !== el) {
100 parent.insertBefore(el, parent.firstChild);
101 }
102}
103
104// @function hasClass(el: HTMLElement, name: String): Boolean
105// Returns `true` if the element's class attribute contains `name`.
106export function hasClass(el, name) {
107 if (el.classList !== undefined) {
108 return el.classList.contains(name);
109 }
110 var className = getClass(el);
111 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
112}
113
114// @function addClass(el: HTMLElement, name: String)
115// Adds `name` to the element's class attribute.
116export function addClass(el, name) {
117 if (el.classList !== undefined) {
118 var classes = Util.splitWords(name);
119 for (var i = 0, len = classes.length; i < len; i++) {
120 el.classList.add(classes[i]);
121 }
122 } else if (!hasClass(el, name)) {
123 var className = getClass(el);
124 setClass(el, (className ? className + ' ' : '') + name);
125 }
126}
127
128// @function removeClass(el: HTMLElement, name: String)
129// Removes `name` from the element's class attribute.
130export function removeClass(el, name) {
131 if (el.classList !== undefined) {
132 el.classList.remove(name);
133 } else {
134 setClass(el, Util.trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
135 }
136}
137
138// @function setClass(el: HTMLElement, name: String)
139// Sets the element's class.
140export function setClass(el, name) {
141 if (el.className.baseVal === undefined) {
142 el.className = name;
143 } else {
144 // in case of SVG element
145 el.className.baseVal = name;
146 }
147}
148
149// @function getClass(el: HTMLElement): String
150// Returns the element's class.
151export function getClass(el) {
152 // Check if the element is an SVGElementInstance and use the correspondingElement instead
153 // (Required for linked SVG elements in IE11.)
154 if (el.correspondingElement) {
155 el = el.correspondingElement;
156 }
157 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
158}
159
160// @function setOpacity(el: HTMLElement, opacity: Number)
161// Set the opacity of an element (including old IE support).
162// `opacity` must be a number from `0` to `1`.
163export function setOpacity(el, value) {
164 if ('opacity' in el.style) {
165 el.style.opacity = value;
166 } else if ('filter' in el.style) {
167 _setOpacityIE(el, value);
168 }
169}
170
171function _setOpacityIE(el, value) {
172 var filter = false,
173 filterName = 'DXImageTransform.Microsoft.Alpha';
174
175 // filters collection throws an error if we try to retrieve a filter that doesn't exist
176 try {
177 filter = el.filters.item(filterName);
178 } catch (e) {
179 // don't set opacity to 1 if we haven't already set an opacity,
180 // it isn't needed and breaks transparent pngs.
181 if (value === 1) { return; }
182 }
183
184 value = Math.round(value * 100);
185
186 if (filter) {
187 filter.Enabled = (value !== 100);
188 filter.Opacity = value;
189 } else {
190 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
191 }
192}
193
194// @function testProp(props: String[]): String|false
195// Goes through the array of style names and returns the first name
196// that is a valid style name for an element. If no such name is found,
197// it returns false. Useful for vendor-prefixed styles like `transform`.
198export function testProp(props) {
199 var style = document.documentElement.style;
200
201 for (var i = 0; i < props.length; i++) {
202 if (props[i] in style) {
203 return props[i];
204 }
205 }
206 return false;
207}
208
209// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
210// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
211// and optionally scaled by `scale`. Does not have an effect if the
212// browser doesn't support 3D CSS transforms.
213export function setTransform(el, offset, scale) {
214 var pos = offset || new Point(0, 0);
215
216 el.style[TRANSFORM] =
217 (Browser.ie3d ?
218 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
219 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
220 (scale ? ' scale(' + scale + ')' : '');
221}
222
223// @function setPosition(el: HTMLElement, position: Point)
224// Sets the position of `el` to coordinates specified by `position`,
225// using CSS translate or top/left positioning depending on the browser
226// (used by Leaflet internally to position its layers).
227export function setPosition(el, point) {
228
229 /*eslint-disable */
230 el._leaflet_pos = point;
231 /* eslint-enable */
232
233 if (Browser.any3d) {
234 setTransform(el, point);
235 } else {
236 el.style.left = point.x + 'px';
237 el.style.top = point.y + 'px';
238 }
239}
240
241// @function getPosition(el: HTMLElement): Point
242// Returns the coordinates of an element previously positioned with setPosition.
243export function getPosition(el) {
244 // this method is only used for elements previously positioned using setPosition,
245 // so it's safe to cache the position for performance
246
247 return el._leaflet_pos || new Point(0, 0);
248}
249
250// @function disableTextSelection()
251// Prevents the user from generating `selectstart` DOM events, usually generated
252// when the user drags the mouse through a page with text. Used internally
253// by Leaflet to override the behaviour of any click-and-drag interaction on
254// the map. Affects drag interactions on the whole document.
255
256// @function enableTextSelection()
257// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
258export var disableTextSelection;
259export var enableTextSelection;
260var _userSelect;
261if ('onselectstart' in document) {
262 disableTextSelection = function () {
263 DomEvent.on(window, 'selectstart', DomEvent.preventDefault);
264 };
265 enableTextSelection = function () {
266 DomEvent.off(window, 'selectstart', DomEvent.preventDefault);
267 };
268} else {
269 var userSelectProperty = testProp(
270 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
271
272 disableTextSelection = function () {
273 if (userSelectProperty) {
274 var style = document.documentElement.style;
275 _userSelect = style[userSelectProperty];
276 style[userSelectProperty] = 'none';
277 }
278 };
279 enableTextSelection = function () {
280 if (userSelectProperty) {
281 document.documentElement.style[userSelectProperty] = _userSelect;
282 _userSelect = undefined;
283 }
284 };
285}
286
287// @function disableImageDrag()
288// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
289// for `dragstart` DOM events, usually generated when the user drags an image.
290export function disableImageDrag() {
291 DomEvent.on(window, 'dragstart', DomEvent.preventDefault);
292}
293
294// @function enableImageDrag()
295// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
296export function enableImageDrag() {
297 DomEvent.off(window, 'dragstart', DomEvent.preventDefault);
298}
299
300var _outlineElement, _outlineStyle;
301// @function preventOutline(el: HTMLElement)
302// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
303// of the element `el` invisible. Used internally by Leaflet to prevent
304// focusable elements from displaying an outline when the user performs a
305// drag interaction on them.
306export function preventOutline(element) {
307 while (element.tabIndex === -1) {
308 element = element.parentNode;
309 }
310 if (!element.style) { return; }
311 restoreOutline();
312 _outlineElement = element;
313 _outlineStyle = element.style.outline;
314 element.style.outline = 'none';
315 DomEvent.on(window, 'keydown', restoreOutline);
316}
317
318// @function restoreOutline()
319// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
320export function restoreOutline() {
321 if (!_outlineElement) { return; }
322 _outlineElement.style.outline = _outlineStyle;
323 _outlineElement = undefined;
324 _outlineStyle = undefined;
325 DomEvent.off(window, 'keydown', restoreOutline);
326}
327
328// @function getSizedParentNode(el: HTMLElement): HTMLElement
329// Finds the closest parent node which size (width and height) is not null.
330export function getSizedParentNode(element) {
331 do {
332 element = element.parentNode;
333 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
334 return element;
335}
336
337// @function getScale(el: HTMLElement): Object
338// Computes the CSS scale currently applied on the element.
339// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
340// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
341export function getScale(element) {
342 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
343
344 return {
345 x: rect.width / element.offsetWidth || 1,
346 y: rect.height / element.offsetHeight || 1,
347 boundingClientRect: rect
348 };
349}