leaflet-defaulticon-compatibility
Version:
Retrieve all Leaflet Default Icon options from CSS, in particular all icon images URL's, to improve compatibility with bundlers and frameworks that modify URL's in CSS.
155 lines (125 loc) • 5.05 kB
JavaScript
import * as L from 'leaflet';
L.Icon.Default.mergeOptions({
// Erase default options, so that they can be overridden by _initializeOptions if not supplied.
iconUrl: null,
iconRetinaUrl: null,
shadowUrl: null,
iconSize: null,
iconAnchor: null,
popupAnchor: null,
tooltipAnchor: null,
shadowSize: null,
// @option classNamePrefix: String = 'leaflet-default-icon-'
// Prefix for the classes defined in CSS that contain the Icon options.
// See the leaflet-defaulticon-compatibility.css file as a starter.
// Expected suffixes are "icon", "shadow", "popup" and "tooltip".
classNamePrefix: 'leaflet-default-icon-',
});
L.Icon.Default.include({
_needsInit: true,
// Override to make sure options are retrieved from CSS.
_getIconUrl: function (name) {
// @option imagePath: String
// `Icon.Default` will try to auto-detect the location of
// the blue icon images. If you are placing these images in a
// non-standard way, set this option to point to the right
// path, before any marker is added to a map.
// Caution: do not use this option with inline base64 image(s).
var imagePath = this.options.imagePath || L.Icon.Default.imagePath || '';
// Deprecated (IconDefault.imagePath), backwards-compatibility only
if (this._needsInit) {
// Modifying imagePath option after _getIconUrl has been called
// once in this instance of IconDefault will no longer have any
// effect.
this._initializeOptions(imagePath);
}
return imagePath + L.Icon.prototype._getIconUrl.call(this, name);
},
// Initialize all necessary options for this instance.
_initializeOptions: function (imagePath) {
this._setOptions('icon', _detectIconOptions, imagePath);
this._setOptions('shadow', _detectIconOptions, imagePath);
this._setOptions('popup', _detectDivOverlayOptions);
this._setOptions('tooltip', _detectDivOverlayOptions);
this._needsInit = false;
},
// Retrieve values from CSS and assign to this instance options.
_setOptions: function (name, detectorFn, imagePath) {
var options = this.options,
prefix = options.classNamePrefix,
optionValues = detectorFn(prefix + name, imagePath);
for (var optionName in optionValues) {
options[name + optionName] = options[name + optionName] || optionValues[optionName];
}
}
});
// Retrieve icon option values from CSS (icon or shadow).
function _detectIconOptions(className, imagePath) {
var el = L.DomUtil.create('div', className, document.body),
urlsContainer = _getBkgImageOrCursor(el),
urls = _extractUrls(urlsContainer, imagePath),
iconX = _getStyleInt(el, 'width'),
iconY = _getStyleInt(el, 'height'),
anchorNX = _getStyleInt(el, 'margin-left'),
anchorNY = _getStyleInt(el, 'margin-top');
el.parentNode.removeChild(el);
return {
Url: urls[0],
RetinaUrl: urls[1],
Size: [iconX, iconY],
Anchor: [-anchorNX, -anchorNY]
};
}
// Retrieve anchor option values from CSS (popup or tooltip).
function _detectDivOverlayOptions(className) {
var el = L.DomUtil.create('div', className, document.body),
anchorX = _getStyleInt(el, 'margin-left'),
anchorY = _getStyleInt(el, 'margin-top');
el.parentNode.removeChild(el);
return {
Anchor: [anchorX, anchorY]
};
}
// Read the CSS url (could be path or inline base64), may be multiple.
// First: normal icon
// Second: Retina icon
function _extractUrls(urlsContainer, imagePath) {
var re = /url\(['"]?([^"']*?)['"]?\)/gi, // Match anything between url( and ), possibly with single or double quotes.
urls = [],
m = re.exec(urlsContainer);
while (m) {
// Keep the entire URL from CSS rule, so that each image can have its own full URL.
// Except in the case imagePath is provided: remove the path part (i.e. keep only the file name).
urls.push(imagePath ? _stripPath(m[1]) : m[1]);
m = re.exec(urlsContainer);
}
return urls;
}
// Remove anything before the last slash (/) occurrence (inclusive).
// Caution: will give unexpected result if url is inline base64 data
// => do not specify imagePath in that case!
function _stripPath(url) {
return url.substr(url.lastIndexOf('/') + 1);
}
function _getStyleInt(el, style) {
return parseInt(_getStyle(el, style), 10);
}
// Factorize style reading fallback for IE8.
function _getStyle(el, style) {
return L.DomUtil.getStyle(el, style) || L.DomUtil.getStyle(el, _kebabToCamelCase(style));
}
// When Firefox high contrast (colours override) option is enabled,
// "background-image" is overridden by the browser as "none".
// In that case, fallback to "cursor". But keep "background-image"
// as primary source because IE expects cursor URL as relative to HTML page
// instead of relative to CSS file.
function _getBkgImageOrCursor(el) {
var bkgImage = _getStyle(el, 'background-image');
return bkgImage && bkgImage !== 'none' ? bkgImage : _getStyle(el, 'cursor');
}
// Convert kebab-case CSS property name to camelCase for IE currentStyle.
function _kebabToCamelCase(prop) {
return prop.replace(/-(\w)/g, function (str, w) {
return w.toUpperCase();
});
}