@erfffun/utils
Version:
Energi javascript utilities for web development
186 lines (177 loc) • 6.22 kB
JavaScript
/* eslint-disable no-underscore-dangle */
import isNode from '../isNode';
import scrollToFn from './scrollto';
const DOCUMENT_TEMPLATE = {
document: {
documentElement: {},
body: {
scrollLeft: 0,
scrollTop: 0,
},
},
};
// eslint-disable-next-line no-undef
const WINDOW = isNode ? global.window || DOCUMENT_TEMPLATE : window;
const DOCUMENT = WINDOW.document;
const BODY = DOCUMENT.body;
const OVERFLOW = 'overflow';
const SCROLL = 'scroll';
const BORDER = 'border';
const WIDTH = 'width';
const _LEFT = '-left';
const _TOP = '-top';
const BORDER_LEFT_WIDTH = `${BORDER + _LEFT}-${WIDTH}`;
const BORDER_RIGHT_WIDTH = `${BORDER}-right-${WIDTH}`;
const BORDER_TOP_WIDTH = `${BORDER + _TOP}-${WIDTH}`;
const BORDER_BOTTOM_WIDTH = `${BORDER}-bottom-${WIDTH}`;
const scrollTo = scrollToFn(WINDOW);
const toCamelCase = (input = '') => {
return input.replace(/-(.)/g, (match, group) => group.toUpperCase());
};
export const nodeLeft = node =>
Math.round(node.getBoundingClientRect().left + BODY.scrollLeft);
export const nodeRight = node => nodeLeft(node) + node.offsetWidth;
export const nodeTop = node =>
Math.round(node.getBoundingClientRect().top + BODY.scrollTop);
export const nodeBottom = node => nodeTop(node) + node.offsetHeight;
/**
* Returns cascaded style of the specified property. `Cascaded` means: the actual present style,
* the way it is visible (calculated through the DOM-tree).
*
* <ul>
* <li>Note1: values are absolute: percentages and points are converted to absolute values, sizes are in pixels, colors in rgb/rgba-format.</li>
* <li>Note2: you cannot query shotcut-properties: use `margin-left` instead of `margin`.</li>
* <li>Note3: no need to camelCase cssProperty: both `margin-left` as well as `marginLeft` are fine.</li>
* <li>Note4: you can query `transition`, `transform`, `perspective` and `transform-origin` instead of their vendor-specific properties.</li>
* <li>Note5: `transition` or `transform` return an Object instead of a String.</li>
* </ul>
*
* @method getNodeStyle
* @param cssProperty {String} property that is queried
* @param [pseudo] {String} to query pseudo-element, fe: `:before` or `:first-line`
* @return {String|Object} value for the css-property: this is an Object for the properties `transition` or `transform`
* @since 0.0.1
*/
// Cautious: when reading the property `transform`, getComputedStyle should
// read the calculated value, but some browsers (webkit) only calculate the style on the current element
// In those cases, we need a patch and look up the tree ourselves
// Also: we will return separate value, NOT matrices
export const getNodeStyle = (node, cssProperty, pseudo) =>
WINDOW.getComputedStyle(node, pseudo)[toCamelCase(cssProperty)];
/**
* Forces the Element to be inside an ancestor-Element that has the `overfow="scroll" set.
*
* @method scrollIntoView
* @param [ancestor] {Element} the Element where it should be forced into its view.
* Only use this when you know the ancestor and this ancestor has an `overflow="scroll"` property
* when not set, this method will seek through the doc-tree upwards for the first Element that does match this criteria.
* @chainable
* @since 0.0.1
*/
export const scrollIntoParentView = (node, ancestor, options = {}) => {
if (!node) {
// eslint-disable-next-line no-console
console.error('scrollIntoParentView undefined node');
return;
}
if (!ancestor) {
// eslint-disable-next-line no-console
console.error('scrollIntoParentView undefined ancestor');
return;
}
const {
transitionTime,
marginLeft = 0,
marginRight = 0,
marginTop = 0,
marginBottom = 0,
} = options;
// eslint-disable-next-line one-var
let parentOverflowNode = node.parentNode,
left,
width,
right,
height,
top,
bottom,
scrollLeft,
scrollTop,
parentOverflowNodeX,
parentOverflowNodeY,
parentOverflowNodeStartTop,
parentOverflowNodeStartLeft,
parentOverflowNodeStopRight,
parentOverflowNodeStopBottom,
newX,
newY;
if (parentOverflowNode) {
if (ancestor) {
parentOverflowNode = ancestor;
} else {
while (
parentOverflowNode &&
parentOverflowNode !== DOCUMENT &&
!(
getNodeStyle(parentOverflowNode, OVERFLOW) === SCROLL ||
getNodeStyle(parentOverflowNode, `${OVERFLOW}-y`) === SCROLL
)
) {
parentOverflowNode = parentOverflowNode.parentNode;
}
}
if (parentOverflowNode && parentOverflowNode !== DOCUMENT) {
left = nodeLeft(node);
width = node.offsetWidth;
right = left + width;
height = node.offsetHeight;
top = nodeTop(node);
bottom = top + height;
scrollLeft =
parentOverflowNode === WINDOW
? BODY.scrollLeft
: parentOverflowNode.scrollLeft;
scrollTop =
parentOverflowNode === WINDOW
? BODY.scrollTop
: parentOverflowNode.scrollTop;
parentOverflowNodeX = nodeLeft(parentOverflowNode);
parentOverflowNodeY = nodeTop(parentOverflowNode);
parentOverflowNodeStartTop =
parentOverflowNodeY +
parseInt(getNodeStyle(parentOverflowNode, BORDER_TOP_WIDTH), 10) +
marginTop;
parentOverflowNodeStartLeft =
parentOverflowNodeX +
parseInt(getNodeStyle(parentOverflowNode, BORDER_LEFT_WIDTH), 10) +
marginLeft;
parentOverflowNodeStopRight =
parentOverflowNodeX +
parentOverflowNode.offsetWidth -
parseInt(getNodeStyle(parentOverflowNode, BORDER_RIGHT_WIDTH), 10) +
marginRight;
parentOverflowNodeStopBottom =
parentOverflowNodeY +
parentOverflowNode.offsetHeight -
parseInt(getNodeStyle(parentOverflowNode, BORDER_BOTTOM_WIDTH), 10) +
marginBottom;
if (left < parentOverflowNodeStartLeft) {
newX = Math.max(0, scrollLeft + left - parentOverflowNodeStartLeft);
} else if (right > parentOverflowNodeStopRight) {
newX = scrollLeft + right - parentOverflowNodeStopRight;
}
if (top < parentOverflowNodeStartTop) {
newY = Math.max(0, scrollTop + top - parentOverflowNodeStartTop);
} else if (bottom > parentOverflowNodeStopBottom) {
newY = scrollTop + bottom - parentOverflowNodeStopBottom;
}
scrollTo(
parentOverflowNode,
scrollLeft,
scrollTop,
newX,
newY,
transitionTime,
);
}
}
};