| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /**
- * Utilities for mouse or touch events.
- */
- import Eventful from './Eventful';
- import env from './env';
- import { ZRRawEvent } from './types';
- import {isCanvasEl, transformCoordWithViewport} from './dom';
- const MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/;
- const _calcOut: number[] = [];
- const firefoxNotSupportOffsetXY = env.browser.firefox
- // use offsetX/offsetY for Firefox >= 39
- // PENDING: consider Firefox for Android and Firefox OS? >= 43
- && +(env.browser.version as string).split('.')[0] < 39;
- type FirefoxMouseEvent = {
- layerX: number
- layerY: number
- }
- /**
- * Get the `zrX` and `zrY`, which are relative to the top-left of
- * the input `el`.
- * CSS transform (2D & 3D) is supported.
- *
- * The strategy to fetch the coords:
- * + If `calculate` is not set as `true`, users of this method should
- * ensure that `el` is the same or the same size & location as `e.target`.
- * Otherwise the result coords are probably not expected. Because we
- * firstly try to get coords from e.offsetX/e.offsetY.
- * + If `calculate` is set as `true`, the input `el` can be any element
- * and we force to calculate the coords based on `el`.
- * + The input `el` should be positionable (not position:static).
- *
- * The force `calculate` can be used in case like:
- * When mousemove event triggered on ec tooltip, `e.target` is not `el`(zr painter.dom).
- *
- * @param el DOM element.
- * @param e Mouse event or touch event.
- * @param out Get `out.zrX` and `out.zrY` as the result.
- * @param calculate Whether to force calculate
- * the coordinates but not use ones provided by browser.
- */
- export function clientToLocal(
- el: HTMLElement,
- e: ZRRawEvent | FirefoxMouseEvent | Touch,
- out: {zrX?: number, zrY?: number},
- calculate?: boolean
- ) {
- out = out || {};
- // According to the W3C Working Draft, offsetX and offsetY should be relative
- // to the padding edge of the target element. The only browser using this convention
- // is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does
- // not support the properties.
- // (see http://www.jacklmoore.com/notes/mouse-position/)
- // In zr painter.dom, padding edge equals to border edge.
- if (calculate) {
- calculateZrXY(el, e as ZRRawEvent, out);
- }
- // Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned
- // ancestor element, so we should make sure el is positioned (e.g., not position:static).
- // BTW1, Webkit don't return the same results as FF in non-simple cases (like add
- // zoom-factor, overflow / opacity layers, transforms ...)
- // BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in preserve-3d.
- // <https://bugs.jquery.com/ticket/8523#comment:14>
- // BTW3, In ff, offsetX/offsetY is always 0.
- else if (firefoxNotSupportOffsetXY
- && (e as FirefoxMouseEvent).layerX != null
- && (e as FirefoxMouseEvent).layerX !== (e as MouseEvent).offsetX
- ) {
- out.zrX = (e as FirefoxMouseEvent).layerX;
- out.zrY = (e as FirefoxMouseEvent).layerY;
- }
- // For IE6+, chrome, safari, opera, firefox >= 39
- else if ((e as MouseEvent).offsetX != null) {
- out.zrX = (e as MouseEvent).offsetX;
- out.zrY = (e as MouseEvent).offsetY;
- }
- // For some other device, e.g., IOS safari.
- else {
- calculateZrXY(el, e as ZRRawEvent, out);
- }
- return out;
- }
- function calculateZrXY(
- el: HTMLElement,
- e: ZRRawEvent,
- out: {zrX?: number, zrY?: number}
- ) {
- // BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect.
- if (env.domSupported && el.getBoundingClientRect) {
- const ex = (e as MouseEvent).clientX;
- const ey = (e as MouseEvent).clientY;
- if (isCanvasEl(el)) {
- // Original approach, which do not support CSS transform.
- // marker can not be locationed in a canvas container
- // (getBoundingClientRect is always 0). We do not support
- // that input a pre-created canvas to zr while using css
- // transform in iOS.
- const box = el.getBoundingClientRect();
- out.zrX = ex - box.left;
- out.zrY = ey - box.top;
- return;
- }
- else {
- if (transformCoordWithViewport(_calcOut, el, ex, ey)) {
- out.zrX = _calcOut[0];
- out.zrY = _calcOut[1];
- return;
- }
- }
- }
- out.zrX = out.zrY = 0;
- }
- /**
- * Find native event compat for legency IE.
- * Should be called at the begining of a native event listener.
- *
- * @param e Mouse event or touch event or pointer event.
- * For lagency IE, we use `window.event` is used.
- * @return The native event.
- */
- export function getNativeEvent(e: ZRRawEvent): ZRRawEvent {
- return e
- || (window.event as any); // For IE
- }
- /**
- * Normalize the coordinates of the input event.
- *
- * Get the `e.zrX` and `e.zrY`, which are relative to the top-left of
- * the input `el`.
- * Get `e.zrDelta` if using mouse wheel.
- * Get `e.which`, see the comment inside this function.
- *
- * Do not calculate repeatly if `zrX` and `zrY` already exist.
- *
- * Notice: see comments in `clientToLocal`. check the relationship
- * between the result coords and the parameters `el` and `calculate`.
- *
- * @param el DOM element.
- * @param e See `getNativeEvent`.
- * @param calculate Whether to force calculate
- * the coordinates but not use ones provided by browser.
- * @return The normalized native UIEvent.
- */
- export function normalizeEvent(
- el: HTMLElement,
- e: ZRRawEvent,
- calculate?: boolean
- ) {
- e = getNativeEvent(e);
- if (e.zrX != null) {
- return e;
- }
- const eventType = e.type;
- const isTouch = eventType && eventType.indexOf('touch') >= 0;
- if (!isTouch) {
- clientToLocal(el, e, e, calculate);
- const wheelDelta = getWheelDeltaMayPolyfill(e);
- // FIXME: IE8- has "wheelDeta" in event "mousewheel" but hat different value (120 times)
- // with Chrome and Safari. It's not correct for zrender event but we left it as it was.
- e.zrDelta = wheelDelta ? wheelDelta / 120 : -(e.detail || 0) / 3;
- }
- else {
- const touch = eventType !== 'touchend'
- ? (<TouchEvent>e).targetTouches[0]
- : (<TouchEvent>e).changedTouches[0];
- touch && clientToLocal(el, touch, e, calculate);
- }
- // Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0;
- // See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js
- // If e.which has been defined, it may be readonly,
- // see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
- const button = (<MouseEvent>e).button;
- if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) {
- (e as any).which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0)));
- }
- // [Caution]: `e.which` from browser is not always reliable. For example,
- // when press left button and `mousemove (pointermove)` in Edge, the `e.which`
- // is 65536 and the `e.button` is -1. But the `mouseup (pointerup)` and
- // `mousedown (pointerdown)` is the same as Chrome does.
- return e;
- }
- // TODO: also provide prop "deltaX" "deltaY" in zrender "mousewheel" event.
- function getWheelDeltaMayPolyfill(e: ZRRawEvent): number {
- // Although event "wheel" do not has the prop "wheelDelta" in spec,
- // agent like Chrome and Safari still provide "wheelDelta" like
- // event "mousewheel" did (perhaps for backward compat).
- // Since zrender has been using "wheelDeta" in zrender event "mousewheel".
- // we currently do not break it.
- // But event "wheel" in firefox do not has "wheelDelta", so we calculate
- // "wheelDeta" from "deltaX", "deltaY" (which is the props in spec).
- const rawWheelDelta = (e as any).wheelDelta;
- // Theroetically `e.wheelDelta` won't be 0 unless some day it has been deprecated
- // by agent like Chrome or Safari. So we also calculate it if rawWheelDelta is 0.
- if (rawWheelDelta) {
- return rawWheelDelta;
- }
- const deltaX = (e as any).deltaX;
- const deltaY = (e as any).deltaY;
- if (deltaX == null || deltaY == null) {
- return rawWheelDelta;
- }
- // Test in Chrome and Safari (MacOS):
- // The sign is corrent.
- // The abs value is 99% corrent (inconsist case only like 62~63, 125~126 ...)
- const delta = deltaY !== 0 ? Math.abs(deltaY) : Math.abs(deltaX);
- const sign = deltaY > 0 ? -1
- : deltaY < 0 ? 1
- : deltaX > 0 ? -1
- : 1;
- return 3 * delta * sign;
- }
- type AddEventListenerParams = Parameters<typeof HTMLElement.prototype.addEventListener>
- type RemoveEventListenerParams = Parameters<typeof HTMLElement.prototype.removeEventListener>
- /**
- * @param el
- * @param name
- * @param handler
- * @param opt If boolean, means `opt.capture`
- * @param opt.capture
- * @param opt.passive
- */
- export function addEventListener(
- el: HTMLElement | HTMLDocument,
- name: AddEventListenerParams[0],
- handler: AddEventListenerParams[1],
- opt?: AddEventListenerParams[2]
- ) {
- // Reproduct the console warning:
- // [Violation] Added non-passive event listener to a scroll-blocking <some> event.
- // Consider marking event handler as 'passive' to make the page more responsive.
- // Just set console log level: verbose in chrome dev tool.
- // then the warning log will be printed when addEventListener called.
- // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
- // We have not yet found a neat way to using passive. Because in zrender the dom event
- // listener delegate all of the upper events of element. Some of those events need
- // to prevent default. For example, the feature `preventDefaultMouseMove` of echarts.
- // Before passive can be adopted, these issues should be considered:
- // (1) Whether and how a zrender user specifies an event listener passive. And by default,
- // passive or not.
- // (2) How to tread that some zrender event listener is passive, and some is not. If
- // we use other way but not preventDefault of mousewheel and touchmove, browser
- // compatibility should be handled.
- // const opts = (env.passiveSupported && name === 'mousewheel')
- // ? {passive: true}
- // // By default, the third param of el.addEventListener is `capture: false`.
- // : void 0;
- // el.addEventListener(name, handler /* , opts */);
- el.addEventListener(name, handler, opt);
- }
- /**
- * Parameter are the same as `addEventListener`.
- *
- * Notice that if a listener is registered twice, one with capture and one without,
- * remove each one separately. Removal of a capturing listener does not affect a
- * non-capturing version of the same listener, and vice versa.
- */
- export function removeEventListener(
- el: HTMLElement | HTMLDocument,
- name: RemoveEventListenerParams[0],
- handler: RemoveEventListenerParams[1],
- opt: RemoveEventListenerParams[2]
- ) {
- el.removeEventListener(name, handler, opt);
- }
- /**
- * preventDefault and stopPropagation.
- * Notice: do not use this method in zrender. It can only be
- * used by upper applications if necessary.
- *
- * @param {Event} e A mouse or touch event.
- */
- export const stop = function (e: MouseEvent | TouchEvent | PointerEvent) {
- e.preventDefault();
- e.stopPropagation();
- e.cancelBubble = true;
- };
- /**
- * This method only works for mouseup and mousedown. The functionality is restricted
- * for fault tolerance, See the `e.which` compatibility above.
- *
- * params can be MouseEvent or ElementEvent
- */
- export function isMiddleOrRightButtonOnMouseUpDown(e: { which: number }) {
- return e.which === 2 || e.which === 3;
- }
- // For backward compatibility
- export {Eventful as Dispatcher};
|