package.control.ZoomSlider.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/control/ZoomSlider
*/
import Control from './Control.js';
import EventType from '../events/EventType.js';
import PointerEventType from '../pointer/EventType.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE} from '../css.js';
import {clamp} from '../math.js';
import {easeOut} from '../easing.js';
import {listen, unlistenByKey} from '../events.js';
import {stopPropagation} from '../events/Event.js';
/**
* The enum for available directions.
*
* @enum {number}
*/
const Direction = {
VERTICAL: 0,
HORIZONTAL: 1,
};
/**
* @typedef {Object} Options
* @property {string} [className='ol-zoomslider'] CSS class name.
* @property {number} [duration=200] Animation duration in milliseconds.
* @property {function(import("../MapEvent.js").default):void} [render] Function called when the control
* should be re-rendered. This is called in a `requestAnimationFrame` callback.
* @property {HTMLElement|string} [target] Specify a target if you want the control to be
* rendered outside of the map's viewport.
*/
/**
* @classdesc
* A slider type of control for zooming.
*
* Example:
*
* map.addControl(new ZoomSlider());
*
* @api
*/
class ZoomSlider extends Control {
/**
* @param {Options} [options] Zoom slider options.
*/
constructor(options) {
options = options ? options : {};
super({
target: options.target,
element: document.createElement('div'),
render: options.render,
});
/**
* @type {!Array}
* @private
*/
this.dragListenerKeys_ = [];
/**
* Will hold the current resolution of the view.
*
* @type {number|undefined}
* @private
*/
this.currentResolution_ = undefined;
/**
* The direction of the slider. Will be determined from actual display of the
* container and defaults to Direction.VERTICAL.
*
* @type {Direction}
* @private
*/
this.direction_ = Direction.VERTICAL;
/**
* @type {boolean}
* @private
*/
this.dragging_;
/**
* @type {number}
* @private
*/
this.heightLimit_ = 0;
/**
* @type {number}
* @private
*/
this.widthLimit_ = 0;
/**
* @type {number|undefined}
* @private
*/
this.startX_;
/**
* @type {number|undefined}
* @private
*/
this.startY_;
/**
* The calculated thumb size (border box plus margins). Set when initSlider_
* is called.
* @type {import("../size.js").Size}
* @private
*/
this.thumbSize_ = null;
/**
* Whether the slider is initialized.
* @type {boolean}
* @private
*/
this.sliderInitialized_ = false;
/**
* @type {number}
* @private
*/
this.duration_ = options.duration !== undefined ? options.duration : 200;
const className =
options.className !== undefined ? options.className : 'ol-zoomslider';
const thumbElement = document.createElement('button');
thumbElement.setAttribute('type', 'button');
thumbElement.className = className + '-thumb ' + CLASS_UNSELECTABLE;
const containerElement = this.element;
containerElement.className =
className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL;
containerElement.appendChild(thumbElement);
containerElement.addEventListener(
PointerEventType.POINTERDOWN,
this.handleDraggerStart_.bind(this),
false,
);
containerElement.addEventListener(
PointerEventType.POINTERMOVE,
this.handleDraggerDrag_.bind(this),
false,
);
containerElement.addEventListener(
PointerEventType.POINTERUP,
this.handleDraggerEnd_.bind(this),
false,
);
containerElement.addEventListener(
EventType.CLICK,
this.handleContainerClick_.bind(this),
false,
);
thumbElement.addEventListener(EventType.CLICK, stopPropagation, false);
}
/**
* Remove the control from its current map and attach it to the new map.
* Pass `null` to just remove the control from the current map.
* Subclasses may set up event handlers to get notified about changes to
* the map here.
* @param {import("../Map.js").default|null} map Map.
* @api
* @override
*/
setMap(map) {
super.setMap(map);
if (map) {
map.render();
}
}
/**
* Initializes the slider element. This will determine and set this controls
* direction_ and also constrain the dragging of the thumb to always be within
* the bounds of the container.
*
* @return {boolean} Initialization successful
* @private
*/
initSlider_() {
const container = this.element;
let containerWidth = container.offsetWidth;
let containerHeight = container.offsetHeight;
if (containerWidth === 0 && containerHeight === 0) {
return (this.sliderInitialized_ = false);
}
const containerStyle = getComputedStyle(container);
containerWidth -=
parseFloat(containerStyle['paddingRight']) +
parseFloat(containerStyle['paddingLeft']);
containerHeight -=
parseFloat(containerStyle['paddingTop']) +
parseFloat(containerStyle['paddingBottom']);
const thumb = /** @type {HTMLElement} */ (container.firstElementChild);
const thumbStyle = getComputedStyle(thumb);
const thumbWidth =
thumb.offsetWidth +
parseFloat(thumbStyle['marginRight']) +
parseFloat(thumbStyle['marginLeft']);
const thumbHeight =
thumb.offsetHeight +
parseFloat(thumbStyle['marginTop']) +
parseFloat(thumbStyle['marginBottom']);
this.thumbSize_ = [thumbWidth, thumbHeight];
if (containerWidth > containerHeight) {
this.direction_ = Direction.HORIZONTAL;
this.widthLimit_ = containerWidth - thumbWidth;
} else {
this.direction_ = Direction.VERTICAL;
this.heightLimit_ = containerHeight - thumbHeight;
}
return (this.sliderInitialized_ = true);
}
/**
* @param {PointerEvent} event The browser event to handle.
* @private
*/
handleContainerClick_(event) {
const view = this.getMap().getView();
const relativePosition = this.getRelativePosition_(
event.offsetX - this.thumbSize_[0] / 2,
event.offsetY - this.thumbSize_[1] / 2,
);
const resolution = this.getResolutionForPosition_(relativePosition);
const zoom = view.getConstrainedZoom(view.getZoomForResolution(resolution));
view.animateInternal({
zoom: zoom,
duration: this.duration_,
easing: easeOut,
});
}
/**
* Handle dragger start events.
* @param {PointerEvent} event The drag event.
* @private
*/
handleDraggerStart_(event) {
if (!this.dragging_ && event.target === this.element.firstElementChild) {
const element = /** @type {HTMLElement} */ (
this.element.firstElementChild
);
this.getMap().getView().beginInteraction();
this.startX_ = event.clientX - parseFloat(element.style.left);
this.startY_ = event.clientY - parseFloat(element.style.top);
this.dragging_ = true;
if (this.dragListenerKeys_.length === 0) {
const drag = this.handleDraggerDrag_;
const end = this.handleDraggerEnd_;
const doc = this.getMap().getOwnerDocument();
this.dragListenerKeys_.push(
listen(doc, PointerEventType.POINTERMOVE, drag, this),
listen(doc, PointerEventType.POINTERUP, end, this),
);
}
}
}
/**
* Handle dragger drag events.
*
* @param {PointerEvent} event The drag event.
* @private
*/
handleDraggerDrag_(event) {
if (this.dragging_) {
const deltaX = event.clientX - this.startX_;
const deltaY = event.clientY - this.startY_;
const relativePosition = this.getRelativePosition_(deltaX, deltaY);
this.currentResolution_ =
this.getResolutionForPosition_(relativePosition);
this.getMap().getView().setResolution(this.currentResolution_);
}
}
/**
* Handle dragger end events.
* @param {PointerEvent} event The drag event.
* @private
*/
handleDraggerEnd_(event) {
if (this.dragging_) {
const view = this.getMap().getView();
view.endInteraction();
this.dragging_ = false;
this.startX_ = undefined;
this.startY_ = undefined;
this.dragListenerKeys_.forEach(unlistenByKey);
this.dragListenerKeys_.length = 0;
}
}
/**
* Positions the thumb inside its container according to the given resolution.
*
* @param {number} res The res.
* @private
*/
setThumbPosition_(res) {
const position = this.getPositionForResolution_(res);
const thumb = /** @type {HTMLElement} */ (this.element.firstElementChild);
if (this.direction_ == Direction.HORIZONTAL) {
thumb.style.left = this.widthLimit_ * position + 'px';
} else {
thumb.style.top = this.heightLimit_ * position + 'px';
}
}
/**
* Calculates the relative position of the thumb given x and y offsets. The
* relative position scales from 0 to 1. The x and y offsets are assumed to be
* in pixel units within the dragger limits.
*
* @param {number} x Pixel position relative to the left of the slider.
* @param {number} y Pixel position relative to the top of the slider.
* @return {number} The relative position of the thumb.
* @private
*/
getRelativePosition_(x, y) {
let amount;
if (this.direction_ === Direction.HORIZONTAL) {
amount = x / this.widthLimit_;
} else {
amount = y / this.heightLimit_;
}
return clamp(amount, 0, 1);
}
/**
* Calculates the corresponding resolution of the thumb given its relative
* position (where 0 is the minimum and 1 is the maximum).
*
* @param {number} position The relative position of the thumb.
* @return {number} The corresponding resolution.
* @private
*/
getResolutionForPosition_(position) {
const fn = this.getMap().getView().getResolutionForValueFunction();
return fn(1 - position);
}
/**
* Determines the relative position of the slider for the given resolution. A
* relative position of 0 corresponds to the minimum view resolution. A
* relative position of 1 corresponds to the maximum view resolution.
*
* @param {number} res The resolution.
* @return {number} The relative position value (between 0 and 1).
* @private
*/
getPositionForResolution_(res) {
const fn = this.getMap().getView().getValueForResolutionFunction();
return clamp(1 - fn(res), 0, 1);
}
/**
* Update the zoomslider element.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @override
*/
render(mapEvent) {
if (!mapEvent.frameState) {
return;
}
if (!this.sliderInitialized_ && !this.initSlider_()) {
return;
}
const res = mapEvent.frameState.viewState.resolution;
this.currentResolution_ = res;
this.setThumbPosition_(res);
}
}
export default ZoomSlider;