From 1c0496e051d8b1af297138732475b1689ada5eb8 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 10 Mar 2016 21:40:07 +0800 Subject: [web] VirtualScroll and AutoScroll helper --- web/src/js/components/helpers/AutoScroll.js | 25 +++++++++ web/src/js/components/helpers/VirtualScroll.js | 70 ++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 web/src/js/components/helpers/AutoScroll.js create mode 100644 web/src/js/components/helpers/VirtualScroll.js (limited to 'web/src/js/components/helpers') diff --git a/web/src/js/components/helpers/AutoScroll.js b/web/src/js/components/helpers/AutoScroll.js new file mode 100644 index 00000000..d37b9f37 --- /dev/null +++ b/web/src/js/components/helpers/AutoScroll.js @@ -0,0 +1,25 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +const symShouldStick = Symbol("shouldStick"); +const isAtBottom = v => v.scrollTop + v.clientHeight === v.scrollHeight; + +export default Component => Object.assign(class AutoScrollWrapper extends Component { + + static displayName = Component.name; + + componentWillUpdate() { + const viewport = ReactDOM.findDOMNode(this); + this[symShouldStick] = viewport.scrollTop && isAtBottom(viewport); + super.componentWillUpdate && super.componentWillUpdate(); + } + + componentDidUpdate() { + const viewport = ReactDOM.findDOMNode(this); + if (this[symShouldStick] && !isAtBottom(viewport)) { + viewport.scrollTop = viewport.scrollHeight; + } + super.componentDidUpdate && super.componentDidUpdate(); + } + +}, Component); diff --git a/web/src/js/components/helpers/VirtualScroll.js b/web/src/js/components/helpers/VirtualScroll.js new file mode 100644 index 00000000..5d4cf796 --- /dev/null +++ b/web/src/js/components/helpers/VirtualScroll.js @@ -0,0 +1,70 @@ +/** + * Calculate virtual scroll stuffs + * + * @param {?Object} opts Options for calculation + * + * @returns {Object} result + * + * __opts__ should have following properties: + * - {number} itemCount + * - {number} rowHeight + * - {number} viewportTop + * - {number} viewportHeight + * - {Array} [itemHeights] + * + * __result__ have following properties: + * - {number} start + * - {number} end + * - {number} paddingTop + * - {number} paddingBottom + */ +export function calcVScroll(opts) { + if (!opts) { + return { start: 0, end: 0, paddingTop: 0, paddingBottom: 0 }; + } + + const { itemCount, rowHeight, viewportTop, viewportHeight, itemHeights } = opts; + const viewportBottom = viewportTop + viewportHeight; + + let start = 0; + let end = 0; + + let paddingTop = 0; + let paddingBottom = 0; + + if (itemHeights) { + + for (let i = 0, pos = 0; i < itemCount; i++) { + const height = itemHeights[i] || rowHeight; + + if (pos <= viewportTop && i % 2 === 0) { + paddingTop = pos; + start = i; + } + + if (pos <= viewportBottom) { + end = i + 1; + } else { + paddingBottom += height; + } + + pos += height; + } + + } else { + + // Make sure that we start at an even row so that CSS `:nth-child(even)` is preserved + start = Math.max(0, Math.floor(viewportTop / rowHeight) - 1) & ~1; + end = Math.min( + itemCount, + start + Math.ceil(viewportHeight / rowHeight) + 1 + ); + + // When a large trunk of elements is removed from the button, start may be far off the viewport. + // To make this issue less severe, limit the top placeholder to the total number of rows. + paddingTop = Math.min(start, itemCount) * rowHeight; + paddingBottom = Math.max(0, itemCount - end) * rowHeight; + } + + return { start, end, paddingTop, paddingBottom }; +} -- cgit v1.2.3