148 lines
4.0 KiB
JavaScript
148 lines
4.0 KiB
JavaScript
|
/**
|
||
|
* Our algorithm have additional one ghost item
|
||
|
* whose index as `data.length` to simplify the calculation
|
||
|
*/
|
||
|
export const GHOST_ITEM_KEY = '__vc_ghost_item__';
|
||
|
|
||
|
/**
|
||
|
* Safari has the elasticity effect which provides negative `scrollTop` value.
|
||
|
* We should ignore it since will make scroll animation shake.
|
||
|
*/
|
||
|
export function alignScrollTop(scrollTop, scrollRange) {
|
||
|
if (scrollTop < 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
if (scrollTop >= scrollRange) {
|
||
|
return scrollRange;
|
||
|
}
|
||
|
|
||
|
return scrollTop;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get node `offsetHeight`. We prefer node is a dom element directly.
|
||
|
* But if not provided, downgrade to `findDOMNode` to get the real dom element.
|
||
|
*/
|
||
|
export function getNodeHeight(node) {
|
||
|
return node ? node.offsetHeight : 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Calculate the located item absolute top with whole scroll height
|
||
|
*/
|
||
|
export function getItemAbsoluteTop({ scrollTop, ...rest }) {
|
||
|
return scrollTop + getItemRelativeTop(rest);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Calculate the located item related top with current window height
|
||
|
*/
|
||
|
export function getItemRelativeTop({
|
||
|
itemIndex,
|
||
|
itemOffsetPtg,
|
||
|
itemElementHeights,
|
||
|
scrollPtg,
|
||
|
clientHeight,
|
||
|
getItemKey,
|
||
|
}) {
|
||
|
const locatedItemHeight = itemElementHeights[getItemKey(itemIndex)] || 0;
|
||
|
const locatedItemTop = scrollPtg * clientHeight;
|
||
|
const locatedItemOffset = itemOffsetPtg * locatedItemHeight;
|
||
|
return Math.floor(locatedItemTop - locatedItemOffset);
|
||
|
}
|
||
|
|
||
|
export function getCompareItemRelativeTop({
|
||
|
locatedItemRelativeTop,
|
||
|
locatedItemIndex,
|
||
|
compareItemIndex,
|
||
|
startIndex,
|
||
|
endIndex,
|
||
|
getItemKey,
|
||
|
itemElementHeights,
|
||
|
}) {
|
||
|
let originCompareItemTop = locatedItemRelativeTop;
|
||
|
const compareItemKey = getItemKey(compareItemIndex);
|
||
|
|
||
|
if (compareItemIndex <= locatedItemIndex) {
|
||
|
for (let index = locatedItemIndex; index >= startIndex; index -= 1) {
|
||
|
const key = getItemKey(index);
|
||
|
if (key === compareItemKey) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const prevItemKey = getItemKey(index - 1);
|
||
|
originCompareItemTop -= itemElementHeights[prevItemKey] || 0;
|
||
|
}
|
||
|
} else {
|
||
|
for (let index = locatedItemIndex; index <= endIndex; index += 1) {
|
||
|
const key = getItemKey(index);
|
||
|
if (key === compareItemKey) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
originCompareItemTop += itemElementHeights[key] || 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return originCompareItemTop;
|
||
|
}
|
||
|
|
||
|
export function getScrollPercentage({ scrollTop, scrollHeight, clientHeight }) {
|
||
|
if (scrollHeight <= clientHeight) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const scrollRange = scrollHeight - clientHeight;
|
||
|
const alignedScrollTop = alignScrollTop(scrollTop, scrollRange);
|
||
|
const scrollTopPtg = alignedScrollTop / scrollRange;
|
||
|
return scrollTopPtg;
|
||
|
}
|
||
|
|
||
|
export function getElementScrollPercentage(element) {
|
||
|
if (!element) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return getScrollPercentage(element);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get location item and its align percentage with the scroll percentage.
|
||
|
* We should measure current scroll position to decide which item is the location item.
|
||
|
* And then fill the top count and bottom count with the base of location item.
|
||
|
*
|
||
|
* `total` should be the real count instead of `total - 1` in calculation.
|
||
|
*/
|
||
|
function getLocationItem(scrollPtg, total) {
|
||
|
const itemIndex = Math.floor(scrollPtg * total);
|
||
|
const itemTopPtg = itemIndex / total;
|
||
|
const itemBottomPtg = (itemIndex + 1) / total;
|
||
|
const itemOffsetPtg = (scrollPtg - itemTopPtg) / (itemBottomPtg - itemTopPtg);
|
||
|
|
||
|
return {
|
||
|
index: itemIndex,
|
||
|
offsetPtg: itemOffsetPtg,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get display items start, end, located item index. This is pure math calculation
|
||
|
*/
|
||
|
export function getRangeIndex(scrollPtg, itemCount, visibleCount) {
|
||
|
const { index, offsetPtg } = getLocationItem(scrollPtg, itemCount);
|
||
|
|
||
|
const beforeCount = Math.ceil(scrollPtg * visibleCount);
|
||
|
const afterCount = Math.ceil((1 - scrollPtg) * visibleCount);
|
||
|
|
||
|
return {
|
||
|
itemIndex: index,
|
||
|
itemOffsetPtg: offsetPtg,
|
||
|
startIndex: Math.max(0, index - beforeCount),
|
||
|
endIndex: Math.min(itemCount - 1, index + afterCount),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function requireVirtual(height, itemHeight, count, virtual) {
|
||
|
return virtual !== false && typeof height === 'number' && count * itemHeight > height;
|
||
|
}
|