dom.js 5.4 KB

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