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

yiminghe / dom-align / 6048810570

01 Sep 2023 11:20AM UTC coverage: 69.328% (+0.9%) from 68.421%
6048810570

push

github

web-flow
Merge pull request #77 from yiminghe/code-coverage

feat: support code coverage

227 of 438 branches covered (0.0%)

382 of 551 relevant lines covered (69.33%)

32.37 hits per line

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

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

9
const RE_NUM = /[-+]?(?:\d*\.|)\d+(?:[eE][-+]?\d+|)/.source;
9✔
10

11
let getComputedStyleX;
12

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

21
const hasOwnProperty = Object.prototype.hasOwnProperty;
9✔
22

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

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

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

57
  x = Math.floor(box.left);
55✔
58
  y = Math.floor(box.top);
55✔
59

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

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

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

80
  x -= docElem.clientLeft || body.clientLeft || 0;
55✔
81
  y -= docElem.clientTop || body.clientTop || 0;
55✔
82

83
  return {
55✔
84
    left: x,
85
    top: y,
86
  };
87
}
88

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

104
function getScrollLeft(w) {
105
  return getScroll(w);
105✔
106
}
107

108
function getScrollTop(w) {
109
  return getScroll(w, true);
105✔
110
}
111

112
function getOffset(el) {
113
  const pos = getClientPosition(el);
55✔
114
  const doc = el.ownerDocument;
55✔
115
  const w = doc.defaultView || doc.parentWindow;
55!
116
  pos.left += getScrollLeft(w);
55✔
117
  pos.top += getScrollTop(w);
55✔
118
  return pos;
55✔
119
}
120

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

131
function getDocument(node) {
132
  if (isWindow(node)) {
570!
133
    return node.document;
×
134
  }
135
  if (node.nodeType === 9) {
570!
136
    return node;
×
137
  }
138
  return node.ownerDocument;
570✔
139
}
140

141
function _getComputedStyle(elem, name, cs) {
142
  let computedStyle = cs;
366✔
143
  let val = '';
366✔
144
  const d = getDocument(elem);
366✔
145
  computedStyle = computedStyle || d.defaultView.getComputedStyle(elem, null);
366✔
146

147
  // https://github.com/kissyteam/kissy/issues/61
148
  if (computedStyle) {
366!
149
    val = computedStyle.getPropertyValue(name) || computedStyle[name];
366✔
150
  }
151

152
  return val;
366✔
153
}
154

155
const _RE_NUM_NO_PX = new RegExp(`^(${RE_NUM})(?!px)[a-z%]+$`, 'i');
9✔
156
const RE_POS = /^(top|right|bottom|left)$/;
9✔
157
const CURRENT_STYLE = 'currentStyle';
9✔
158
const RUNTIME_STYLE = 'runtimeStyle';
9✔
159
const LEFT = 'left';
9✔
160
const PX = 'px';
9✔
161

162
function _getComputedStyleIE(elem, name) {
163
  // currentStyle maybe null
164
  // http://msdn.microsoft.com/en-us/library/ms535231.aspx
165
  let ret = elem[CURRENT_STYLE] && elem[CURRENT_STYLE][name];
×
166

167
  // 当 width/height 设置为百分比时,通过 pixelLeft 方式转换的 width/height 值
168
  // 一开始就处理了! CUSTOM_STYLE.height,CUSTOM_STYLE.width ,cssHook 解决@2011-08-19
169
  // 在 ie 下不对,需要直接用 offset 方式
170
  // borderWidth 等值也有问题,但考虑到 borderWidth 设为百分比的概率很小,这里就不考虑了
171

172
  // From the awesome hack by Dean Edwards
173
  // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
174
  // If we're not dealing with a regular pixel number
175
  // but a number that has a weird ending, we need to convert it to pixels
176
  // exclude left right for relativity
177
  if (_RE_NUM_NO_PX.test(ret) && !RE_POS.test(name)) {
×
178
    // Remember the original values
179
    const style = elem.style;
×
180
    const left = style[LEFT];
×
181
    const rsLeft = elem[RUNTIME_STYLE][LEFT];
×
182

183
    // prevent flashing of content
184
    elem[RUNTIME_STYLE][LEFT] = elem[CURRENT_STYLE][LEFT];
×
185

186
    // Put in the new values to get a computed value out
187
    style[LEFT] = name === 'fontSize' ? '1em' : ret || 0;
×
188
    ret = style.pixelLeft + PX;
×
189

190
    // Revert the changed values
191
    style[LEFT] = left;
×
192

193
    elem[RUNTIME_STYLE][LEFT] = rsLeft;
×
194
  }
195
  return ret === '' ? 'auto' : ret;
×
196
}
197

198
if (typeof window !== 'undefined') {
9!
199
  getComputedStyleX = window.getComputedStyle
9!
200
    ? _getComputedStyle
201
    : _getComputedStyleIE;
202
}
203

204
function getOffsetDirection(dir, option) {
205
  if (dir === 'left') {
66✔
206
    return option.useCssRight ? 'right' : dir;
33!
207
  }
208
  return option.useCssBottom ? 'bottom' : dir;
33!
209
}
210

211
function oppositeOffsetDirection(dir) {
212
  if (dir === 'left') {
22✔
213
    return 'right';
11✔
214
  } else if (dir === 'right') {
11!
215
    return 'left';
×
216
  } else if (dir === 'top') {
11!
217
    return 'bottom';
11✔
218
  } else if (dir === 'bottom') {
×
219
    return 'top';
×
220
  }
221
}
222

223
// 设置 elem 相对 elem.ownerDocument 的坐标
224
function setLeftTop(elem, offset, option) {
225
  // set position first, in-case top/left are set even on static elem
226
  if (css(elem, 'position') === 'static') {
11!
227
    elem.style.position = 'relative';
×
228
  }
229
  let presetH = -999;
11✔
230
  let presetV = -999;
11✔
231
  const horizontalProperty = getOffsetDirection('left', option);
11✔
232
  const verticalProperty = getOffsetDirection('top', option);
11✔
233
  const oppositeHorizontalProperty =
234
    oppositeOffsetDirection(horizontalProperty);
11✔
235
  const oppositeVerticalProperty = oppositeOffsetDirection(verticalProperty);
11✔
236

237
  if (horizontalProperty !== 'left') {
11!
238
    presetH = 999;
×
239
  }
240

241
  if (verticalProperty !== 'top') {
11!
242
    presetV = 999;
×
243
  }
244
  let originalTransition = '';
11✔
245
  const originalOffset = getOffset(elem);
11✔
246
  if ('left' in offset || 'top' in offset) {
11!
247
    originalTransition = getTransitionProperty(elem) || '';
11✔
248
    setTransitionProperty(elem, 'none');
11✔
249
  }
250
  if ('left' in offset) {
11!
251
    elem.style[oppositeHorizontalProperty] = '';
11✔
252
    elem.style[horizontalProperty] = `${presetH}px`;
11✔
253
  }
254
  if ('top' in offset) {
11!
255
    elem.style[oppositeVerticalProperty] = '';
11✔
256
    elem.style[verticalProperty] = `${presetV}px`;
11✔
257
  }
258
  // force relayout
259
  forceRelayout(elem);
11✔
260
  const old = getOffset(elem);
11✔
261
  const originalStyle = {};
11✔
262
  for (const key in offset) {
11✔
263
    if (hasOwnProperty.call(offset, key)) {
22!
264
      const dir = getOffsetDirection(key, option);
22✔
265
      const preset = key === 'left' ? presetH : presetV;
22✔
266
      const off = originalOffset[key] - old[key];
22✔
267
      if (dir === key) {
22!
268
        originalStyle[dir] = preset + off;
22✔
269
      } else {
270
        originalStyle[dir] = preset - off;
×
271
      }
272
    }
273
  }
274
  css(elem, originalStyle);
11✔
275
  // force relayout
276
  forceRelayout(elem);
11✔
277
  if ('left' in offset || 'top' in offset) {
11!
278
    setTransitionProperty(elem, originalTransition);
11✔
279
  }
280
  const ret = {};
11✔
281
  for (const key in offset) {
11✔
282
    if (hasOwnProperty.call(offset, key)) {
22!
283
      const dir = getOffsetDirection(key, option);
22✔
284
      const off = offset[key] - originalOffset[key];
22✔
285
      if (key === dir) {
22!
286
        ret[dir] = originalStyle[dir] + off;
22✔
287
      } else {
288
        ret[dir] = originalStyle[dir] - off;
×
289
      }
290
    }
291
  }
292
  css(elem, ret);
11✔
293
}
294

295
function setTransform(elem, offset) {
296
  const originalOffset = getOffset(elem);
×
297
  const originalXY = getTransformXY(elem);
×
298
  const resultXY = { x: originalXY.x, y: originalXY.y };
×
299
  if ('left' in offset) {
×
300
    resultXY.x = originalXY.x + offset.left - originalOffset.left;
×
301
  }
302
  if ('top' in offset) {
×
303
    resultXY.y = originalXY.y + offset.top - originalOffset.top;
×
304
  }
305
  setTransformXY(elem, resultXY);
×
306
}
307

308
function setOffset(elem, offset, option) {
309
  if (option.ignoreShake) {
11!
310
    const oriOffset = getOffset(elem);
×
311

312
    const oLeft = oriOffset.left.toFixed(0);
×
313
    const oTop = oriOffset.top.toFixed(0);
×
314
    const tLeft = offset.left.toFixed(0);
×
315
    const tTop = offset.top.toFixed(0);
×
316

317
    if (oLeft === tLeft && oTop === tTop) {
×
318
      return;
×
319
    }
320
  }
321

322
  if (option.useCssRight || option.useCssBottom) {
11!
323
    setLeftTop(elem, offset, option);
×
324
  } else if (
11!
325
    option.useCssTransform &&
11!
326
    getTransformName() in document.body.style
327
  ) {
328
    setTransform(elem, offset, option);
×
329
  } else {
330
    setLeftTop(elem, offset, option);
11✔
331
  }
332
}
333

334
function each(arr, fn) {
335
  for (let i = 0; i < arr.length; i++) {
24✔
336
    fn(arr[i]);
48✔
337
  }
338
}
339

340
function isBorderBoxFn(elem) {
341
  return getComputedStyleX(elem, 'boxSizing') === 'border-box';
30✔
342
}
343

344
const BOX_MODELS = ['margin', 'border', 'padding'];
9✔
345
const CONTENT_INDEX = -1;
9✔
346
const PADDING_INDEX = 2;
9✔
347
const BORDER_INDEX = 1;
9✔
348
const MARGIN_INDEX = 0;
9✔
349

350
function swap(elem, options, callback) {
351
  const old = {};
×
352
  const style = elem.style;
×
353
  let name;
354

355
  // Remember the old values, and insert the new ones
356
  for (name in options) {
×
357
    if (hasOwnProperty.call(options, name)) {
×
358
      old[name] = style[name];
×
359
      style[name] = options[name];
×
360
    }
361
  }
362

363
  callback.call(elem);
×
364

365
  // Revert the old values
366
  for (name in options) {
×
367
    if (hasOwnProperty.call(options, name)) {
×
368
      style[name] = old[name];
×
369
    }
370
  }
371
}
372

373
function getPBMWidth(elem, props, which) {
374
  let value = 0;
×
375
  let prop;
376
  let j;
377
  let i;
378
  for (j = 0; j < props.length; j++) {
×
379
    prop = props[j];
×
380
    if (prop) {
×
381
      for (i = 0; i < which.length; i++) {
×
382
        let cssProp;
383
        if (prop === 'border') {
×
384
          cssProp = `${prop}${which[i]}Width`;
×
385
        } else {
386
          cssProp = prop + which[i];
×
387
        }
388
        value += parseFloat(getComputedStyleX(elem, cssProp)) || 0;
×
389
      }
390
    }
391
  }
392
  return value;
×
393
}
394

395
const domUtils = {
9✔
396
  getParent(element) {
397
    let parent = element;
260✔
398
    do {
260✔
399
      if (parent.nodeType === 11 && parent.host) {
260!
400
        parent = parent.host;
×
401
      } else {
402
        parent = parent.parentNode;
260✔
403
      }
404
    } while (parent && parent.nodeType !== 1 && parent.nodeType !== 9);
520!
405
    return parent;
260✔
406
  },
407
};
408

409
each(['Width', 'Height'], (name) => {
9✔
410
  domUtils[`doc${name}`] = (refWin) => {
18✔
411
    const d = refWin.document;
×
412
    return Math.max(
×
413
      // firefox chrome documentElement.scrollHeight< body.scrollHeight
414
      // ie standard mode : documentElement.scrollHeight> body.scrollHeight
415
      d.documentElement[`scroll${name}`],
416
      // quirks : documentElement.scrollHeight 最大等于可视窗口多一点?
417
      d.body[`scroll${name}`],
418
      domUtils[`viewport${name}`](d),
419
    );
420
  };
421

422
  domUtils[`viewport${name}`] = (win) => {
18✔
423
    // pc browser includes scrollbar in window.innerWidth
424
    const prop = `client${name}`;
100✔
425
    const doc = win.document;
100✔
426
    const body = doc.body;
100✔
427
    const documentElement = doc.documentElement;
100✔
428
    const documentElementProp = documentElement[prop];
100✔
429
    // 标准模式取 documentElement
430
    // backcompat 取 body
431
    return (
100✔
432
      (doc.compatMode === 'CSS1Compat' && documentElementProp) ||
200!
433
      (body && body[prop]) ||
434
      documentElementProp
435
    );
436
  };
437
});
438

439
/*
440
 得到元素的大小信息
441
 @param elem
442
 @param name
443
 @param {String} [extra]  'padding' : (css width) + padding
444
 'border' : (css width) + padding + border
445
 'margin' : (css width) + padding + border + margin
446
 */
447
function getWH(elem, name, ex) {
448
  let extra = ex;
30✔
449
  if (isWindow(elem)) {
30!
450
    return name === 'width'
×
451
      ? domUtils.viewportWidth(elem)
452
      : domUtils.viewportHeight(elem);
453
  } else if (elem.nodeType === 9) {
30!
454
    return name === 'width'
×
455
      ? domUtils.docWidth(elem)
456
      : domUtils.docHeight(elem);
457
  }
458
  const which = name === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
30✔
459
  let borderBoxValue =
460
    name === 'width'
30✔
461
      ? Math.floor(elem.getBoundingClientRect().width)
462
      : Math.floor(elem.getBoundingClientRect().height);
463
  const isBorderBox = isBorderBoxFn(elem);
30✔
464
  let cssBoxValue = 0;
30✔
465
  if (
30!
466
    borderBoxValue === null ||
90✔
467
    borderBoxValue === undefined ||
468
    borderBoxValue <= 0
469
  ) {
470
    borderBoxValue = undefined;
×
471
    // Fall back to computed then un computed css if necessary
472
    cssBoxValue = getComputedStyleX(elem, name);
×
473
    if (
×
474
      cssBoxValue === null ||
×
475
      cssBoxValue === undefined ||
476
      Number(cssBoxValue) < 0
477
    ) {
478
      cssBoxValue = elem.style[name] || 0;
×
479
    }
480
    // Normalize '', auto, and prepare for extra
481
    cssBoxValue = Math.floor(parseFloat(cssBoxValue)) || 0;
×
482
  }
483
  if (extra === undefined) {
30!
484
    extra = isBorderBox ? BORDER_INDEX : CONTENT_INDEX;
×
485
  }
486
  const borderBoxValueOrIsBorderBox =
487
    borderBoxValue !== undefined || isBorderBox;
30!
488
  const val = borderBoxValue || cssBoxValue;
30!
489
  if (extra === CONTENT_INDEX) {
30!
490
    if (borderBoxValueOrIsBorderBox) {
×
491
      return val - getPBMWidth(elem, ['border', 'padding'], which);
×
492
    }
493
    return cssBoxValue;
×
494
  } else if (borderBoxValueOrIsBorderBox) {
30!
495
    if (extra === BORDER_INDEX) {
30!
496
      return val;
30✔
497
    }
498
    return (
×
499
      val +
500
      (extra === PADDING_INDEX
×
501
        ? -getPBMWidth(elem, ['border'], which)
502
        : getPBMWidth(elem, ['margin'], which))
503
    );
504
  }
505
  return cssBoxValue + getPBMWidth(elem, BOX_MODELS.slice(extra), which);
×
506
}
507

508
const cssShow = {
9✔
509
  position: 'absolute',
510
  visibility: 'hidden',
511
  display: 'block',
512
};
513

514
// fix #119 : https://github.com/kissyteam/kissy/issues/119
515
function getWHIgnoreDisplay(...args) {
516
  let val;
517
  const elem = args[0];
30✔
518
  // in case elem is window
519
  // elem.offsetWidth === undefined
520
  if (elem.offsetWidth !== 0) {
30!
521
    val = getWH.apply(undefined, args);
30✔
522
  } else {
523
    swap(elem, cssShow, () => {
×
524
      val = getWH.apply(undefined, args);
×
525
    });
526
  }
527
  return val;
30✔
528
}
529

530
each(['width', 'height'], (name) => {
9✔
531
  const first = name.charAt(0).toUpperCase() + name.slice(1);
18✔
532
  domUtils[`outer${first}`] = (el, includeMargin) => {
18✔
533
    return (
30✔
534
      el &&
60✔
535
      getWHIgnoreDisplay(el, name, includeMargin ? MARGIN_INDEX : BORDER_INDEX)
30!
536
    );
537
  };
538
  const which = name === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
18✔
539

540
  domUtils[name] = (elem, v) => {
18✔
541
    let val = v;
×
542
    if (val !== undefined) {
×
543
      if (elem) {
×
544
        const isBorderBox = isBorderBoxFn(elem);
×
545
        if (isBorderBox) {
×
546
          val += getPBMWidth(elem, ['padding', 'border'], which);
×
547
        }
548
        return css(elem, name, val);
×
549
      }
550
      return undefined;
×
551
    }
552
    return elem && getWHIgnoreDisplay(elem, name, CONTENT_INDEX);
×
553
  };
554
});
555

556
function mix(to, from) {
557
  for (const i in from) {
34✔
558
    if (hasOwnProperty.call(from, i)) {
153!
559
      to[i] = from[i];
153✔
560
    }
561
  }
562
  return to;
34✔
563
}
564

565
const utils = {
9✔
566
  getWindow(node) {
567
    if (node && node.document && node.setTimeout) {
×
568
      return node;
×
569
    }
570
    const doc = node.ownerDocument || node;
×
571
    return doc.defaultView || doc.parentWindow;
×
572
  },
573
  getDocument,
574
  offset(el, value, option) {
575
    if (typeof value !== 'undefined') {
44✔
576
      setOffset(el, value, option || {});
11!
577
    } else {
578
      return getOffset(el);
33✔
579
    }
580
  },
581
  isWindow,
582
  each,
583
  css,
584
  clone(obj) {
585
    let i;
586
    const ret = {};
×
587
    for (i in obj) {
×
588
      if (hasOwnProperty.call(obj, i)) {
×
589
        ret[i] = obj[i];
×
590
      }
591
    }
592
    const overflow = obj.overflow;
×
593
    if (overflow) {
×
594
      for (i in obj) {
×
595
        if (hasOwnProperty.call(obj, i)) {
×
596
          ret.overflow[i] = obj.overflow[i];
×
597
        }
598
      }
599
    }
600
    return ret;
×
601
  },
602
  mix,
603
  getWindowScrollLeft(w) {
604
    return getScrollLeft(w);
50✔
605
  },
606
  getWindowScrollTop(w) {
607
    return getScrollTop(w);
50✔
608
  },
609
  merge(...args) {
610
    const ret = {};
11✔
611
    for (let i = 0; i < args.length; i++) {
11✔
612
      utils.mix(ret, args[i]);
22✔
613
    }
614
    return ret;
11✔
615
  },
616
  viewportWidth: 0,
617
  viewportHeight: 0,
618
};
619

620
mix(utils, domUtils);
9✔
621

622
export default utils;
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