• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

react-component / dom-align / 10520691070

23 Aug 2024 05:59AM UTC coverage: 62.108%. First build
10520691070

push

github

web-flow
Update .github/workflows/ci.yml

202 of 382 branches covered (52.88%)

Branch coverage included in aggregate %.

352 of 510 relevant lines covered (69.02%)

32.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.06
/src/utils.ts
1
import {
2
  setTransitionProperty,
3
  getTransitionProperty,
4
  getTransformXY,
5
  setTransformXY,
6
  getTransformName,
7
} from './propertyUtils';
8

9
// https://stackoverflow.com/a/3485654/3040605
10
function forceRelayout(elem) {
11
  const originalStyle = elem.style.display;
22✔
12
  elem.style.display = 'none';
22✔
13
  elem.offsetHeight; // eslint-disable-line
22✔
14
  elem.style.display = originalStyle;
22✔
15
}
16

17
const hasOwnProperty = Object.prototype.hasOwnProperty;
9✔
18

19
function css(el, name, v = undefined) {
358✔
20
  let value = v;
402✔
21
  if (typeof name === 'object') {
402✔
22
    for (const i in name) {
22✔
23
      if (hasOwnProperty.call(name, i)) {
44!
24
        css(el, i, name[i]);
44✔
25
      }
26
    }
27
    return undefined;
22✔
28
  }
29
  if (typeof value !== 'undefined') {
380✔
30
    if (typeof value === 'number') {
44!
31
      value = `${value}px`;
44✔
32
    }
33
    el.style[name] = value;
44✔
34
    return undefined;
44✔
35
  }
36
  return getComputedStyle(el, name);
336✔
37
}
38

39
function getClientPosition(elem) {
40
  let x;
41
  let y;
42
  const doc = elem.ownerDocument;
55✔
43
  const body = doc.body;
55✔
44
  const docElem = doc && doc.documentElement;
55✔
45
  // 根据 GBS 最新数据,A-Grade Browsers 都已支持 getBoundingClientRect 方法,不用再考虑传统的实现方式
46
  const box = elem.getBoundingClientRect();
55✔
47

48
  // 注:jQuery 还考虑减去 docElem.clientLeft/clientTop
49
  // 但测试发现,这样反而会导致当 html 和 body 有边距/边框样式时,获取的值不正确
50
  // 此外,ie6 会忽略 html 的 margin 值,幸运地是没有谁会去设置 html 的 margin
51

52
  x = Math.floor(box.left);
55✔
53
  y = Math.floor(box.top);
55✔
54

55
  // In IE, most of the time, 2 extra pixels are added to the top and left
56
  // due to the implicit 2-pixel inset border.  In IE6/7 quirks mode and
57
  // IE6 standards mode, this border can be overridden by setting the
58
  // document element's border to zero -- thus, we cannot rely on the
59
  // offset always being 2 pixels.
60

61
  // In quirks mode, the offset can be determined by querying the body's
62
  // clientLeft/clientTop, but in standards mode, it is found by querying
63
  // the document element's clientLeft/clientTop.  Since we already called
64
  // getClientBoundingRect we have already forced a reflow, so it is not
65
  // too expensive just to query them all.
66

67
  // ie 下应该减去窗口的边框吧,毕竟默认 absolute 都是相对窗口定位的
68
  // 窗口边框标准是设 documentElement ,quirks 时设置 body
69
  // 最好禁止在 body 和 html 上边框 ,但 ie < 9 html 默认有 2px ,减去
70
  // 但是非 ie 不可能设置窗口边框,body html 也不是窗口 ,ie 可以通过 html,body 设置
71
  // 标准 ie 下 docElem.clientTop 就是 border-top
72
  // ie7 html 即窗口边框改变不了。永远为 2
73
  // 但标准 firefox/chrome/ie9 下 docElem.clientTop 是窗口边框,即使设了 border-top 也为 0
74

75
  x -= docElem.clientLeft || body.clientLeft || 0;
55✔
76
  y -= docElem.clientTop || body.clientTop || 0;
55✔
77

78
  return {
55✔
79
    left: x,
80
    top: y,
81
  };
82
}
83

84
function getScroll(w, top: boolean = false) {
105✔
85
  let ret = w[`page${top ? 'Y' : 'X'}Offset`];
210✔
86
  const method = `scroll${top ? 'Top' : 'Left'}`;
210✔
87
  if (typeof ret !== 'number') {
210!
88
    const d = w.document;
×
89
    // ie6,7,8 standard mode
90
    ret = d.documentElement[method];
×
91
    if (typeof ret !== 'number') {
×
92
      // quirks mode
93
      ret = d.body[method];
×
94
    }
95
  }
96
  return ret;
210✔
97
}
98

99
function getScrollLeft(w) {
100
  return getScroll(w);
105✔
101
}
102

103
function getScrollTop(w) {
104
  return getScroll(w, true);
105✔
105
}
106

107
function getOffset(el) {
108
  const pos = getClientPosition(el);
55✔
109
  const doc = el.ownerDocument;
55✔
110
  const w = doc.defaultView || doc.parentWindow;
55!
111
  pos.left += getScrollLeft(w);
55✔
112
  pos.top += getScrollTop(w);
55✔
113
  return pos;
55✔
114
}
115

116
/**
117
 * A crude way of determining if an object is a window
118
 * @member util
119
 */
120
function isWindow(obj) {
121
  // must use == for ie8
122
  /* eslint eqeqeq:0 */
123
  return obj !== null && obj !== undefined && obj == obj.window;
810✔
124
}
125

126
function getDocument(node) {
127
  if (isWindow(node)) {
570!
128
    return node.document;
×
129
  }
130
  if (node.nodeType === 9) {
570!
131
    return node;
×
132
  }
133
  return node.ownerDocument;
570✔
134
}
135

136
function getComputedStyle(elem, name) {
137
  let val = '';
366✔
138
  const d = getDocument(elem);
366✔
139
  const computedStyle = d.defaultView.getComputedStyle(elem, null);
366✔
140

141
  // https://github.com/kissyteam/kissy/issues/61
142
  if (computedStyle) {
366!
143
    val = computedStyle.getPropertyValue(name) || computedStyle[name];
366✔
144
  }
145

146
  return val;
366✔
147
}
148

149
function getOffsetDirection(dir, option) {
150
  if (dir === 'left') {
66✔
151
    return option.useCssRight ? 'right' : dir;
33✔
152
  }
153
  return option.useCssBottom ? 'bottom' : dir;
33✔
154
}
155

156
function oppositeOffsetDirection(dir) {
157
  if (dir === 'left') {
22✔
158
    return 'right';
11✔
159
  } else if (dir === 'right') {
11!
160
    return 'left';
×
161
  } else if (dir === 'top') {
11!
162
    return 'bottom';
11✔
163
  } else if (dir === 'bottom') {
×
164
    return 'top';
×
165
  }
166
}
167

168
// 设置 elem 相对 elem.ownerDocument 的坐标
169
function setLeftTop(elem, offset, option) {
170
  // set position first, in-case top/left are set even on static elem
171
  if (css(elem, 'position') === 'static') {
11!
172
    elem.style.position = 'relative';
×
173
  }
174
  let presetH = -999;
11✔
175
  let presetV = -999;
11✔
176
  const horizontalProperty = getOffsetDirection('left', option);
11✔
177
  const verticalProperty = getOffsetDirection('top', option);
11✔
178
  const oppositeHorizontalProperty = oppositeOffsetDirection(horizontalProperty);
11✔
179
  const oppositeVerticalProperty = oppositeOffsetDirection(verticalProperty);
11✔
180

181
  if (horizontalProperty !== 'left') {
11!
182
    presetH = 999;
×
183
  }
184

185
  if (verticalProperty !== 'top') {
11!
186
    presetV = 999;
×
187
  }
188
  let originalTransition = '';
11✔
189
  const originalOffset = getOffset(elem);
11✔
190
  if ('left' in offset || 'top' in offset) {
11!
191
    originalTransition = getTransitionProperty(elem) || '';
11✔
192
    setTransitionProperty(elem, 'none');
11✔
193
  }
194
  if ('left' in offset) {
11!
195
    elem.style[oppositeHorizontalProperty] = '';
11✔
196
    elem.style[horizontalProperty] = `${presetH}px`;
11✔
197
  }
198
  if ('top' in offset) {
11!
199
    elem.style[oppositeVerticalProperty] = '';
11✔
200
    elem.style[verticalProperty] = `${presetV}px`;
11✔
201
  }
202
  // force relayout
203
  forceRelayout(elem);
11✔
204
  const old = getOffset(elem);
11✔
205
  const originalStyle = {};
11✔
206
  for (const key in offset) {
11✔
207
    if (hasOwnProperty.call(offset, key)) {
22!
208
      const dir = getOffsetDirection(key, option);
22✔
209
      const preset = key === 'left' ? presetH : presetV;
22✔
210
      const off = originalOffset[key] - old[key];
22✔
211
      if (dir === key) {
22!
212
        originalStyle[dir] = preset + off;
22✔
213
      } else {
214
        originalStyle[dir] = preset - off;
×
215
      }
216
    }
217
  }
218
  css(elem, originalStyle);
11✔
219
  // force relayout
220
  forceRelayout(elem);
11✔
221
  if ('left' in offset || 'top' in offset) {
11!
222
    setTransitionProperty(elem, originalTransition);
11✔
223
  }
224
  const ret = {};
11✔
225
  for (const key in offset) {
11✔
226
    if (hasOwnProperty.call(offset, key)) {
22!
227
      const dir = getOffsetDirection(key, option);
22✔
228
      const off = offset[key] - originalOffset[key];
22✔
229
      if (key === dir) {
22!
230
        ret[dir] = originalStyle[dir] + off;
22✔
231
      } else {
232
        ret[dir] = originalStyle[dir] - off;
×
233
      }
234
    }
235
  }
236
  css(elem, ret);
11✔
237
}
238

239
function setTransform(elem, offset) {
240
  const originalOffset = getOffset(elem);
×
241
  const originalXY = getTransformXY(elem);
×
242
  const resultXY = { x: originalXY.x, y: originalXY.y };
×
243
  if ('left' in offset) {
×
244
    resultXY.x = originalXY.x + offset.left - originalOffset.left;
×
245
  }
246
  if ('top' in offset) {
×
247
    resultXY.y = originalXY.y + offset.top - originalOffset.top;
×
248
  }
249
  setTransformXY(elem, resultXY);
×
250
}
251

252
function setOffset(elem, offset, option) {
253
  if (option.ignoreShake) {
11!
254
    const oriOffset = getOffset(elem);
×
255

256
    const oLeft = oriOffset.left.toFixed(0);
×
257
    const oTop = oriOffset.top.toFixed(0);
×
258
    const tLeft = offset.left.toFixed(0);
×
259
    const tTop = offset.top.toFixed(0);
×
260

261
    if (oLeft === tLeft && oTop === tTop) {
×
262
      return;
×
263
    }
264
  }
265

266
  if (option.useCssRight || option.useCssBottom) {
11!
267
    setLeftTop(elem, offset, option);
×
268
  } else if (option.useCssTransform && getTransformName() in document.body.style) {
11!
269
    setTransform(elem, offset);
×
270
  } else {
271
    setLeftTop(elem, offset, option);
11✔
272
  }
273
}
274

275
function each(arr, fn) {
276
  for (let i = 0; i < arr.length; i++) {
24✔
277
    fn(arr[i]);
48✔
278
  }
279
}
280

281
function isBorderBoxFn(elem) {
282
  return getComputedStyle(elem, 'boxSizing') === 'border-box';
30✔
283
}
284

285
const BOX_MODELS = ['margin', 'border', 'padding'];
9✔
286
const CONTENT_INDEX = -1;
9✔
287
const PADDING_INDEX = 2;
9✔
288
const BORDER_INDEX = 1;
9✔
289
const MARGIN_INDEX = 0;
9✔
290

291
function swap(elem, options, callback) {
292
  const old = {};
×
293
  const style = elem.style;
×
294
  let name;
295

296
  // Remember the old values, and insert the new ones
297
  for (name in options) {
×
298
    if (hasOwnProperty.call(options, name)) {
×
299
      old[name] = style[name];
×
300
      style[name] = options[name];
×
301
    }
302
  }
303

304
  callback.call(elem);
×
305

306
  // Revert the old values
307
  for (name in options) {
×
308
    if (hasOwnProperty.call(options, name)) {
×
309
      style[name] = old[name];
×
310
    }
311
  }
312
}
313

314
function getPBMWidth(elem, props, which) {
315
  let value = 0;
×
316
  let prop;
317
  let j;
318
  let i;
319
  for (j = 0; j < props.length; j++) {
×
320
    prop = props[j];
×
321
    if (prop) {
×
322
      for (i = 0; i < which.length; i++) {
×
323
        let cssProp;
324
        if (prop === 'border') {
×
325
          cssProp = `${prop}${which[i]}Width`;
×
326
        } else {
327
          cssProp = prop + which[i];
×
328
        }
329
        value += parseFloat(getComputedStyle(elem, cssProp)) || 0;
×
330
      }
331
    }
332
  }
333
  return value;
×
334
}
335

336
const domUtils = {
9✔
337
  getParent(element) {
338
    let parent = element;
260✔
339
    do {
260✔
340
      if (parent.nodeType === 11 && parent.host) {
260!
341
        parent = parent.host;
×
342
      } else {
343
        parent = parent.parentNode;
260✔
344
      }
345
    } while (parent && parent.nodeType !== 1 && parent.nodeType !== 9);
520!
346
    return parent;
260✔
347
  },
348
};
349

350
each(['Width', 'Height'], (name) => {
9✔
351
  domUtils[`doc${name}`] = (refWin) => {
18✔
352
    const d = refWin.document;
×
353
    return Math.max(
×
354
      // firefox chrome documentElement.scrollHeight< body.scrollHeight
355
      // ie standard mode : documentElement.scrollHeight> body.scrollHeight
356
      d.documentElement[`scroll${name}`],
357
      // quirks : documentElement.scrollHeight 最大等于可视窗口多一点?
358
      d.body[`scroll${name}`],
359
      domUtils[`viewport${name}`](d),
360
    );
361
  };
362

363
  domUtils[`viewport${name}`] = (win) => {
18✔
364
    // pc browser includes scrollbar in window.innerWidth
365
    const prop = `client${name}`;
100✔
366
    const doc = win.document;
100✔
367
    const body = doc.body;
100✔
368
    const documentElement = doc.documentElement;
100✔
369
    const documentElementProp = documentElement[prop];
100✔
370
    // 标准模式取 documentElement
371
    // backcompat 取 body
372
    return (
100✔
373
      (doc.compatMode === 'CSS1Compat' && documentElementProp) ||
200!
374
      (body && body[prop]) ||
375
      documentElementProp
376
    );
377
  };
378
});
379

380
/*
381
 得到元素的大小信息
382
 @param elem
383
 @param name
384
 @param {String} [extra]  'padding' : (css width) + padding
385
 'border' : (css width) + padding + border
386
 'margin' : (css width) + padding + border + margin
387
 */
388
function getWH(elem, name, ex) {
389
  let extra = ex;
30✔
390
  if (isWindow(elem)) {
30!
391
    return name === 'width'
×
392
      ? (domUtils as any).viewportWidth(elem)
393
      : (domUtils as any).viewportHeight(elem);
394
  } else if (elem.nodeType === 9) {
30!
395
    return name === 'width' ? (domUtils as any).docWidth(elem) : (domUtils as any).docHeight(elem);
×
396
  }
397
  const which = name === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
30✔
398
  let borderBoxValue =
399
    name === 'width'
30✔
400
      ? Math.floor(elem.getBoundingClientRect().width)
401
      : Math.floor(elem.getBoundingClientRect().height);
402
  const isBorderBox = isBorderBoxFn(elem);
30✔
403
  let cssBoxValue = 0 as any;
30✔
404
  if (borderBoxValue === null || borderBoxValue === undefined || borderBoxValue <= 0) {
30!
405
    borderBoxValue = undefined;
×
406
    // Fall back to computed then un computed css if necessary
407
    cssBoxValue = getComputedStyle(elem, name);
×
408
    if (cssBoxValue === null || cssBoxValue === undefined || Number(cssBoxValue) < 0) {
×
409
      cssBoxValue = elem.style[name] || 0;
×
410
    }
411
    // Normalize '', auto, and prepare for extra
412
    cssBoxValue = Math.floor(parseFloat(cssBoxValue.toString())) || 0;
×
413
  }
414
  if (extra === undefined) {
30!
415
    extra = isBorderBox ? BORDER_INDEX : CONTENT_INDEX;
×
416
  }
417
  const borderBoxValueOrIsBorderBox = borderBoxValue !== undefined || isBorderBox;
30!
418
  const val = borderBoxValue || cssBoxValue;
30!
419
  if (extra === CONTENT_INDEX) {
30!
420
    if (borderBoxValueOrIsBorderBox) {
×
421
      return val - getPBMWidth(elem, ['border', 'padding'], which);
×
422
    }
423
    return cssBoxValue;
×
424
  } else if (borderBoxValueOrIsBorderBox) {
30!
425
    if (extra === BORDER_INDEX) {
30!
426
      return val;
30✔
427
    }
428
    return (
×
429
      val +
430
      (extra === PADDING_INDEX
431
        ? -getPBMWidth(elem, ['border'], which)
432
        : getPBMWidth(elem, ['margin'], which))
433
    );
434
  }
435
  return cssBoxValue + getPBMWidth(elem, BOX_MODELS.slice(extra), which);
×
436
}
437

438
const cssShow = {
9✔
439
  position: 'absolute',
440
  visibility: 'hidden',
441
  display: 'block',
442
};
443

444
// fix #119 : https://github.com/kissyteam/kissy/issues/119
445
function getWHIgnoreDisplay(...args) {
446
  let val;
447
  const elem = args[0];
30✔
448
  // in case elem is window
449
  // elem.offsetWidth === undefined
450
  if (elem.offsetWidth !== 0) {
30!
451
    // eslint-disable-next-line prefer-spread
452
    val = getWH.apply(undefined, args);
30✔
453
  } else {
454
    swap(elem, cssShow, () => {
×
455
      // eslint-disable-next-line prefer-spread
456
      val = getWH.apply(undefined, args);
×
457
    });
458
  }
459
  return val;
30✔
460
}
461

462
each(['width', 'height'], (name) => {
9✔
463
  const first = name.charAt(0).toUpperCase() + name.slice(1);
18✔
464
  domUtils[`outer${first}`] = (el, includeMargin) => {
18✔
465
    return el && getWHIgnoreDisplay(el, name, includeMargin ? MARGIN_INDEX : BORDER_INDEX);
30✔
466
  };
467
  const which = name === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
18✔
468

469
  domUtils[name] = (elem, v) => {
18✔
470
    let val = v;
×
471
    if (val !== undefined) {
×
472
      if (elem) {
×
473
        const isBorderBox = isBorderBoxFn(elem);
×
474
        if (isBorderBox) {
×
475
          val += getPBMWidth(elem, ['padding', 'border'], which);
×
476
        }
477
        return css(elem, name, val);
×
478
      }
479
      return undefined;
×
480
    }
481
    return elem && getWHIgnoreDisplay(elem, name, CONTENT_INDEX);
×
482
  };
483
});
484

485
const utils = {
9✔
486
  getWindow(node) {
487
    if (node && node.document && node.setTimeout) {
×
488
      return node;
×
489
    }
490
    const doc = node.ownerDocument || node;
×
491
    return doc.defaultView || doc.parentWindow;
×
492
  },
493
  getDocument,
494
  offset(el, value, option) {
495
    if (typeof value !== 'undefined') {
44✔
496
      setOffset(el, value, option || {});
11!
497
    } else {
498
      return getOffset(el);
33✔
499
    }
500
  },
501
  isWindow,
502
  each,
503
  css,
504
  clone(obj) {
505
    let i;
506
    const ret = {} as any;
×
507
    for (i in obj) {
×
508
      if (hasOwnProperty.call(obj, i)) {
×
509
        ret[i] = obj[i];
×
510
      }
511
    }
512
    const overflow = obj.overflow;
×
513
    if (overflow) {
×
514
      for (i in obj) {
×
515
        if (hasOwnProperty.call(obj, i)) {
×
516
          ret.overflow[i] = obj.overflow[i];
×
517
        }
518
      }
519
    }
520
    return ret;
×
521
  },
522
  getWindowScrollLeft(w) {
523
    return getScrollLeft(w);
50✔
524
  },
525
  getWindowScrollTop(w) {
526
    return getScrollTop(w);
50✔
527
  },
528
  viewportWidth: 0,
529
  viewportHeight: 0,
530
  ...domUtils,
531
};
532

533
export default utils as any;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc