091022e2a7f5108094544e49f2141358cdd239b5ca6af9aaa2422bbec7efdc12a8ca9125ebd33a4c06e9b1de9221fbc3e202fcf844c7884ee9cbcd60de74aa 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import env from './env';
  2. import {buildTransformer} from './fourPointsTransform';
  3. import {Dictionary} from './types';
  4. const EVENT_SAVED_PROP = '___zrEVENTSAVED';
  5. const _calcOut: number[] = [];
  6. type SavedInfo = {
  7. markers?: HTMLDivElement[]
  8. trans?: ReturnType<typeof buildTransformer>
  9. invTrans?: ReturnType<typeof buildTransformer>
  10. srcCoords?: number[]
  11. }
  12. /**
  13. * Transform "local coord" from `elFrom` to `elTarget`.
  14. * "local coord": the coord based on the input `el`. The origin point is at
  15. * the position of "left: 0; top: 0;" in the `el`.
  16. *
  17. * Support when CSS transform is used.
  18. *
  19. * Having the `out` (that is, `[outX, outY]`), we can create an DOM element
  20. * and set the CSS style as "left: outX; top: outY;" and append it to `elTarge`
  21. * to locate the element.
  22. *
  23. * For example, this code below positions a child of `document.body` on the event
  24. * point, no matter whether `body` has `margin`/`paddin`/`transfrom`/... :
  25. * ```js
  26. * transformLocalCoord(out, container, document.body, event.offsetX, event.offsetY);
  27. * if (!eqNaN(out[0])) {
  28. * // Then locate the tip element on the event point.
  29. * var tipEl = document.createElement('div');
  30. * tipEl.style.cssText = 'position: absolute; left:' + out[0] + ';top:' + out[1] + ';';
  31. * document.body.appendChild(tipEl);
  32. * }
  33. * ```
  34. *
  35. * Notice: In some env this method is not supported. If called, `out` will be `[NaN, NaN]`.
  36. *
  37. * @param {Array.<number>} out [inX: number, inY: number] The output..
  38. * If can not transform, `out` will not be modified but return `false`.
  39. * @param {HTMLElement} elFrom The `[inX, inY]` is based on elFrom.
  40. * @param {HTMLElement} elTarget The `out` is based on elTarget.
  41. * @param {number} inX
  42. * @param {number} inY
  43. * @return {boolean} Whether transform successfully.
  44. */
  45. export function transformLocalCoord(
  46. out: number[],
  47. elFrom: HTMLElement,
  48. elTarget: HTMLElement,
  49. inX: number,
  50. inY: number
  51. ) {
  52. return transformCoordWithViewport(_calcOut, elFrom, inX, inY, true)
  53. && transformCoordWithViewport(out, elTarget, _calcOut[0], _calcOut[1]);
  54. }
  55. /**
  56. * Transform between a "viewport coord" and a "local coord".
  57. * "viewport coord": the coord based on the left-top corner of the viewport
  58. * of the browser.
  59. * "local coord": the coord based on the input `el`. The origin point is at
  60. * the position of "left: 0; top: 0;" in the `el`.
  61. *
  62. * Support the case when CSS transform is used on el.
  63. *
  64. * @param out [inX: number, inY: number] The output. If `inverse: false`,
  65. * it represents "local coord", otherwise "vireport coord".
  66. * If can not transform, `out` will not be modified but return `false`.
  67. * @param el The "local coord" is based on the `el`, see comment above.
  68. * @param inX If `inverse: false`,
  69. * it represents "vireport coord", otherwise "local coord".
  70. * @param inY If `inverse: false`,
  71. * it represents "vireport coord", otherwise "local coord".
  72. * @param inverse
  73. * `true`: from "viewport coord" to "local coord".
  74. * `false`: from "local coord" to "viewport coord".
  75. * @return {boolean} Whether transform successfully.
  76. */
  77. export function transformCoordWithViewport(
  78. out: number[],
  79. el: HTMLElement,
  80. inX: number,
  81. inY: number,
  82. inverse?: boolean
  83. ) {
  84. if (el.getBoundingClientRect && env.domSupported && !isCanvasEl(el)) {
  85. const saved = (el as any)[EVENT_SAVED_PROP] || ((el as any)[EVENT_SAVED_PROP] = {});
  86. const markers = prepareCoordMarkers(el, saved);
  87. const transformer = preparePointerTransformer(markers, saved, inverse);
  88. if (transformer) {
  89. transformer(out, inX, inY);
  90. return true;
  91. }
  92. }
  93. return false;
  94. }
  95. function prepareCoordMarkers(el: HTMLElement, saved: SavedInfo) {
  96. let markers = saved.markers;
  97. if (markers) {
  98. return markers;
  99. }
  100. markers = saved.markers = [];
  101. const propLR = ['left', 'right'];
  102. const propTB = ['top', 'bottom'];
  103. for (let i = 0; i < 4; i++) {
  104. const marker = document.createElement('div');
  105. const stl = marker.style;
  106. const idxLR = i % 2;
  107. const idxTB = (i >> 1) % 2;
  108. stl.cssText = [
  109. 'position: absolute',
  110. 'visibility: hidden',
  111. 'padding: 0',
  112. 'margin: 0',
  113. 'border-width: 0',
  114. 'user-select: none',
  115. 'width:0',
  116. 'height:0',
  117. // 'width: 5px',
  118. // 'height: 5px',
  119. propLR[idxLR] + ':0',
  120. propTB[idxTB] + ':0',
  121. propLR[1 - idxLR] + ':auto',
  122. propTB[1 - idxTB] + ':auto',
  123. ''
  124. ].join('!important;');
  125. el.appendChild(marker);
  126. markers.push(marker);
  127. }
  128. return markers;
  129. }
  130. function preparePointerTransformer(markers: HTMLDivElement[], saved: SavedInfo, inverse?: boolean) {
  131. const transformerName: 'invTrans' | 'trans' = inverse ? 'invTrans' : 'trans';
  132. const transformer = saved[transformerName];
  133. const oldSrcCoords = saved.srcCoords;
  134. const srcCoords = [];
  135. const destCoords = [];
  136. let oldCoordTheSame = true;
  137. for (let i = 0; i < 4; i++) {
  138. const rect = markers[i].getBoundingClientRect();
  139. const ii = 2 * i;
  140. const x = rect.left;
  141. const y = rect.top;
  142. srcCoords.push(x, y);
  143. oldCoordTheSame = oldCoordTheSame && oldSrcCoords && x === oldSrcCoords[ii] && y === oldSrcCoords[ii + 1];
  144. destCoords.push(markers[i].offsetLeft, markers[i].offsetTop);
  145. }
  146. // Cache to avoid time consuming of `buildTransformer`.
  147. return (oldCoordTheSame && transformer)
  148. ? transformer
  149. : (
  150. saved.srcCoords = srcCoords,
  151. saved[transformerName] = inverse
  152. ? buildTransformer(destCoords, srcCoords)
  153. : buildTransformer(srcCoords, destCoords)
  154. );
  155. }
  156. export function isCanvasEl(el: HTMLElement): el is HTMLCanvasElement {
  157. return el.nodeName.toUpperCase() === 'CANVAS';
  158. }
  159. const replaceReg = /([&<>"'])/g;
  160. const replaceMap: Dictionary<string> = {
  161. '&': '&amp;',
  162. '<': '&lt;',
  163. '>': '&gt;',
  164. '"': '&quot;',
  165. '\'': '&#39;'
  166. };
  167. export function encodeHTML(source: string): string {
  168. return source == null
  169. ? ''
  170. : (source + '').replace(replaceReg, function (str, c) {
  171. return replaceMap[c];
  172. });
  173. }