package.src.core.event.ts Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zrender Show documentation
Show all versions of zrender Show documentation
A lightweight graphic library providing 2d draw for Apache ECharts
The newest version!
/**
* 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.
//
// 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'
? (e).targetTouches[0]
: (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 = (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
type RemoveEventListenerParams = Parameters
/**
* @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 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};
© 2015 - 2025 Weber Informatics LLC | Privacy Policy