zrender.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /*!
  2. * ZRender, a high performance 2d drawing library.
  3. *
  4. * Copyright (c) 2013, Baidu Inc.
  5. * All rights reserved.
  6. *
  7. * LICENSE
  8. * https://github.com/ecomfe/zrender/blob/master/LICENSE.txt
  9. */
  10. import env from './core/env';
  11. import * as zrUtil from './core/util';
  12. import Handler from './Handler';
  13. import Storage from './Storage';
  14. import {PainterBase} from './PainterBase';
  15. import Animation, {getTime} from './animation/Animation';
  16. import HandlerProxy from './dom/HandlerProxy';
  17. import Element, { ElementEventCallback } from './Element';
  18. import { Dictionary, ElementEventName, RenderedEvent, WithThisType } from './core/types';
  19. import { LayerConfig } from './canvas/Layer';
  20. import { GradientObject } from './graphic/Gradient';
  21. import { PatternObject } from './graphic/Pattern';
  22. import { EventCallback } from './core/Eventful';
  23. import Displayable from './graphic/Displayable';
  24. import { lum } from './tool/color';
  25. import { DARK_MODE_THRESHOLD } from './config';
  26. import Group from './graphic/Group';
  27. type PainterBaseCtor = {
  28. new(dom: HTMLElement, storage: Storage, ...args: any[]): PainterBase
  29. }
  30. const painterCtors: Dictionary<PainterBaseCtor> = {};
  31. let instances: { [key: number]: ZRender } = {};
  32. function delInstance(id: number) {
  33. delete instances[id];
  34. }
  35. function isDarkMode(backgroundColor: string | GradientObject | PatternObject): boolean {
  36. if (!backgroundColor) {
  37. return false;
  38. }
  39. if (typeof backgroundColor === 'string') {
  40. return lum(backgroundColor, 1) < DARK_MODE_THRESHOLD;
  41. }
  42. else if ((backgroundColor as GradientObject).colorStops) {
  43. const colorStops = (backgroundColor as GradientObject).colorStops;
  44. let totalLum = 0;
  45. const len = colorStops.length;
  46. // Simply do the math of average the color. Not consider the offset
  47. for (let i = 0; i < len; i++) {
  48. totalLum += lum(colorStops[i].color, 1);
  49. }
  50. totalLum /= len;
  51. return totalLum < DARK_MODE_THRESHOLD;
  52. }
  53. // Can't determine
  54. return false;
  55. }
  56. class ZRender {
  57. /**
  58. * Not necessary if using SSR painter like svg-ssr
  59. */
  60. dom?: HTMLElement
  61. id: number
  62. storage: Storage
  63. painter: PainterBase
  64. handler: Handler
  65. animation: Animation
  66. private _sleepAfterStill = 10;
  67. private _stillFrameAccum = 0;
  68. private _needsRefresh = true
  69. private _needsRefreshHover = true
  70. private _disposed: boolean;
  71. /**
  72. * If theme is dark mode. It will determine the color strategy for labels.
  73. */
  74. private _darkMode = false;
  75. private _backgroundColor: string | GradientObject | PatternObject;
  76. constructor(id: number, dom?: HTMLElement, opts?: ZRenderInitOpt) {
  77. opts = opts || {};
  78. /**
  79. * @type {HTMLDomElement}
  80. */
  81. this.dom = dom;
  82. this.id = id;
  83. const storage = new Storage();
  84. let rendererType = opts.renderer || 'canvas';
  85. if (!painterCtors[rendererType]) {
  86. // Use the first registered renderer.
  87. rendererType = zrUtil.keys(painterCtors)[0];
  88. }
  89. if (process.env.NODE_ENV !== 'production') {
  90. if (!painterCtors[rendererType]) {
  91. throw new Error(`Renderer '${rendererType}' is not imported. Please import it first.`);
  92. }
  93. }
  94. opts.useDirtyRect = opts.useDirtyRect == null
  95. ? false
  96. : opts.useDirtyRect;
  97. const painter = new painterCtors[rendererType](dom, storage, opts, id);
  98. const ssrMode = opts.ssr || painter.ssrOnly;
  99. this.storage = storage;
  100. this.painter = painter;
  101. const handlerProxy = (!env.node && !env.worker && !ssrMode)
  102. ? new HandlerProxy(painter.getViewportRoot(), painter.root)
  103. : null;
  104. const useCoarsePointer = opts.useCoarsePointer;
  105. const usePointerSize = (useCoarsePointer == null || useCoarsePointer === 'auto')
  106. ? env.touchEventsSupported
  107. : !!useCoarsePointer;
  108. const defaultPointerSize = 44;
  109. let pointerSize;
  110. if (usePointerSize) {
  111. pointerSize = zrUtil.retrieve2(opts.pointerSize, defaultPointerSize);
  112. }
  113. this.handler = new Handler(storage, painter, handlerProxy, painter.root, pointerSize);
  114. this.animation = new Animation({
  115. stage: {
  116. update: ssrMode ? null : () => this._flush(true)
  117. }
  118. });
  119. if (!ssrMode) {
  120. this.animation.start();
  121. }
  122. }
  123. /**
  124. * 添加元素
  125. */
  126. add(el: Element) {
  127. if (this._disposed || !el) {
  128. return;
  129. }
  130. this.storage.addRoot(el);
  131. el.addSelfToZr(this);
  132. this.refresh();
  133. }
  134. /**
  135. * 删除元素
  136. */
  137. remove(el: Element) {
  138. if (this._disposed || !el) {
  139. return;
  140. }
  141. this.storage.delRoot(el);
  142. el.removeSelfFromZr(this);
  143. this.refresh();
  144. }
  145. /**
  146. * Change configuration of layer
  147. */
  148. configLayer(zLevel: number, config: LayerConfig) {
  149. if (this._disposed) {
  150. return;
  151. }
  152. if (this.painter.configLayer) {
  153. this.painter.configLayer(zLevel, config);
  154. }
  155. this.refresh();
  156. }
  157. /**
  158. * Set background color
  159. */
  160. setBackgroundColor(backgroundColor: string | GradientObject | PatternObject) {
  161. if (this._disposed) {
  162. return;
  163. }
  164. if (this.painter.setBackgroundColor) {
  165. this.painter.setBackgroundColor(backgroundColor);
  166. }
  167. this.refresh();
  168. this._backgroundColor = backgroundColor;
  169. this._darkMode = isDarkMode(backgroundColor);
  170. }
  171. getBackgroundColor() {
  172. return this._backgroundColor;
  173. }
  174. /**
  175. * Force to set dark mode
  176. */
  177. setDarkMode(darkMode: boolean) {
  178. this._darkMode = darkMode;
  179. }
  180. isDarkMode() {
  181. return this._darkMode;
  182. }
  183. /**
  184. * Repaint the canvas immediately
  185. */
  186. refreshImmediately(fromInside?: boolean) {
  187. if (this._disposed) {
  188. return;
  189. }
  190. // const start = new Date();
  191. if (!fromInside) {
  192. // Update animation if refreshImmediately is invoked from outside.
  193. // Not trigger stage update to call flush again. Which may refresh twice
  194. this.animation.update(true);
  195. }
  196. // Clear needsRefresh ahead to avoid something wrong happens in refresh
  197. // Or it will cause zrender refreshes again and again.
  198. this._needsRefresh = false;
  199. this.painter.refresh();
  200. // Avoid trigger zr.refresh in Element#beforeUpdate hook
  201. this._needsRefresh = false;
  202. }
  203. /**
  204. * Mark and repaint the canvas in the next frame of browser
  205. */
  206. refresh() {
  207. if (this._disposed) {
  208. return;
  209. }
  210. this._needsRefresh = true;
  211. // Active the animation again.
  212. this.animation.start();
  213. }
  214. /**
  215. * Perform all refresh
  216. */
  217. flush() {
  218. if (this._disposed) {
  219. return;
  220. }
  221. this._flush(false);
  222. }
  223. private _flush(fromInside?: boolean) {
  224. let triggerRendered;
  225. const start = getTime();
  226. if (this._needsRefresh) {
  227. triggerRendered = true;
  228. this.refreshImmediately(fromInside);
  229. }
  230. if (this._needsRefreshHover) {
  231. triggerRendered = true;
  232. this.refreshHoverImmediately();
  233. }
  234. const end = getTime();
  235. if (triggerRendered) {
  236. this._stillFrameAccum = 0;
  237. this.trigger('rendered', {
  238. elapsedTime: end - start
  239. } as RenderedEvent);
  240. }
  241. else if (this._sleepAfterStill > 0) {
  242. this._stillFrameAccum++;
  243. // Stop the animation after still for 10 frames.
  244. if (this._stillFrameAccum > this._sleepAfterStill) {
  245. this.animation.stop();
  246. }
  247. }
  248. }
  249. /**
  250. * Set sleep after still for frames.
  251. * Disable auto sleep when it's 0.
  252. */
  253. setSleepAfterStill(stillFramesCount: number) {
  254. this._sleepAfterStill = stillFramesCount;
  255. }
  256. /**
  257. * Wake up animation loop. But not render.
  258. */
  259. wakeUp() {
  260. if (this._disposed) {
  261. return;
  262. }
  263. this.animation.start();
  264. // Reset the frame count.
  265. this._stillFrameAccum = 0;
  266. }
  267. /**
  268. * Refresh hover in next frame
  269. */
  270. refreshHover() {
  271. this._needsRefreshHover = true;
  272. }
  273. /**
  274. * Refresh hover immediately
  275. */
  276. refreshHoverImmediately() {
  277. if (this._disposed) {
  278. return;
  279. }
  280. this._needsRefreshHover = false;
  281. if (this.painter.refreshHover && this.painter.getType() === 'canvas') {
  282. this.painter.refreshHover();
  283. }
  284. }
  285. /**
  286. * Resize the canvas.
  287. * Should be invoked when container size is changed
  288. */
  289. resize(opts?: {
  290. width?: number| string
  291. height?: number | string
  292. }) {
  293. if (this._disposed) {
  294. return;
  295. }
  296. opts = opts || {};
  297. this.painter.resize(opts.width, opts.height);
  298. this.handler.resize();
  299. }
  300. /**
  301. * Stop and clear all animation immediately
  302. */
  303. clearAnimation() {
  304. if (this._disposed) {
  305. return;
  306. }
  307. this.animation.clear();
  308. }
  309. /**
  310. * Get container width
  311. */
  312. getWidth(): number | undefined {
  313. if (this._disposed) {
  314. return;
  315. }
  316. return this.painter.getWidth();
  317. }
  318. /**
  319. * Get container height
  320. */
  321. getHeight(): number | undefined {
  322. if (this._disposed) {
  323. return;
  324. }
  325. return this.painter.getHeight();
  326. }
  327. /**
  328. * Set default cursor
  329. * @param cursorStyle='default' 例如 crosshair
  330. */
  331. setCursorStyle(cursorStyle: string) {
  332. if (this._disposed) {
  333. return;
  334. }
  335. this.handler.setCursorStyle(cursorStyle);
  336. }
  337. /**
  338. * Find hovered element
  339. * @param x
  340. * @param y
  341. * @return {target, topTarget}
  342. */
  343. findHover(x: number, y: number): {
  344. target: Displayable
  345. topTarget: Displayable
  346. } | undefined {
  347. if (this._disposed) {
  348. return;
  349. }
  350. return this.handler.findHover(x, y);
  351. }
  352. on<Ctx>(eventName: ElementEventName, eventHandler: ElementEventCallback<Ctx, ZRenderType>, context?: Ctx): this
  353. // eslint-disable-next-line max-len
  354. on<Ctx>(eventName: string, eventHandler: WithThisType<EventCallback<any[]>, unknown extends Ctx ? ZRenderType : Ctx>, context?: Ctx): this
  355. // eslint-disable-next-line max-len
  356. on<Ctx>(eventName: string, eventHandler: (...args: any) => any, context?: Ctx): this {
  357. if (!this._disposed) {
  358. this.handler.on(eventName, eventHandler, context);
  359. }
  360. return this;
  361. }
  362. /**
  363. * Unbind event
  364. * @param eventName Event name
  365. * @param eventHandler Handler function
  366. */
  367. // eslint-disable-next-line max-len
  368. off(eventName?: string, eventHandler?: EventCallback) {
  369. if (this._disposed) {
  370. return;
  371. }
  372. this.handler.off(eventName, eventHandler);
  373. }
  374. /**
  375. * Trigger event manually
  376. *
  377. * @param eventName Event name
  378. * @param event Event object
  379. */
  380. trigger(eventName: string, event?: unknown) {
  381. if (this._disposed) {
  382. return;
  383. }
  384. this.handler.trigger(eventName, event);
  385. }
  386. /**
  387. * Clear all objects and the canvas.
  388. */
  389. clear() {
  390. if (this._disposed) {
  391. return;
  392. }
  393. const roots = this.storage.getRoots();
  394. for (let i = 0; i < roots.length; i++) {
  395. if (roots[i] instanceof Group) {
  396. roots[i].removeSelfFromZr(this);
  397. }
  398. }
  399. this.storage.delAllRoots();
  400. this.painter.clear();
  401. }
  402. /**
  403. * Dispose self.
  404. */
  405. dispose() {
  406. if (this._disposed) {
  407. return;
  408. }
  409. this.animation.stop();
  410. this.clear();
  411. this.storage.dispose();
  412. this.painter.dispose();
  413. this.handler.dispose();
  414. this.animation =
  415. this.storage =
  416. this.painter =
  417. this.handler = null;
  418. this._disposed = true;
  419. delInstance(this.id);
  420. }
  421. }
  422. export interface ZRenderInitOpt {
  423. renderer?: string // 'canvas' or 'svg
  424. devicePixelRatio?: number
  425. width?: number | string // 10, 10px, 'auto'
  426. height?: number | string
  427. useDirtyRect?: boolean
  428. useCoarsePointer?: 'auto' | boolean
  429. pointerSize?: number
  430. ssr?: boolean // If enable ssr mode.
  431. }
  432. /**
  433. * Initializing a zrender instance
  434. *
  435. * @param dom Not necessary if using SSR painter like svg-ssr
  436. */
  437. export function init(dom?: HTMLElement | null, opts?: ZRenderInitOpt) {
  438. const zr = new ZRender(zrUtil.guid(), dom, opts);
  439. instances[zr.id] = zr;
  440. return zr;
  441. }
  442. /**
  443. * Dispose zrender instance
  444. */
  445. export function dispose(zr: ZRender) {
  446. zr.dispose();
  447. }
  448. /**
  449. * Dispose all zrender instances
  450. */
  451. export function disposeAll() {
  452. for (let key in instances) {
  453. if (instances.hasOwnProperty(key)) {
  454. instances[key].dispose();
  455. }
  456. }
  457. instances = {};
  458. }
  459. /**
  460. * Get zrender instance by id
  461. */
  462. export function getInstance(id: number): ZRender {
  463. return instances[id];
  464. }
  465. export function registerPainter(name: string, Ctor: PainterBaseCtor) {
  466. painterCtors[name] = Ctor;
  467. }
  468. export type ElementSSRData = zrUtil.HashMap<unknown>;
  469. export type ElementSSRDataGetter<T> = (el: Element) => zrUtil.HashMap<T>;
  470. let ssrDataGetter: ElementSSRDataGetter<unknown>;
  471. export function getElementSSRData(el: Element): ElementSSRData {
  472. if (typeof ssrDataGetter === 'function') {
  473. return ssrDataGetter(el);
  474. }
  475. }
  476. export function registerSSRDataGetter<T>(getter: ElementSSRDataGetter<T>) {
  477. ssrDataGetter = getter;
  478. }
  479. /**
  480. * @type {string}
  481. */
  482. export const version = '5.5.0';
  483. export interface ZRenderType extends ZRender {};