render only visible ChatListItems

This commit is contained in:
Martti Malmi 2021-02-08 15:14:43 +02:00
parent 0f9363d779
commit db3bd8cfbe
4 changed files with 115 additions and 9 deletions

View File

@ -10,7 +10,7 @@
<meta property="og:description" content="Social Networking Freedom" />
<meta property="og:url" content="https://iris.to" />
<meta property="og:image" content="https://iris.to/img/cover.jpg" />
<meta name="twitter:card" content="summary"></meta>
<meta name="apple-mobile-web-app-capable" content="yes">

View File

@ -6,6 +6,7 @@ import Helpers from '../Helpers.js';
import Session from '../Session.js';
import { route } from '../lib/preact-router.es.js';
import Notifications from '../Notifications.js';
import ScrollViewport from '../lib/preact-scroll-viewport.js';
class ChatList extends Component {
constructor() {
@ -56,14 +57,16 @@ class ChatList extends Component {
</svg>
${t('new_chat')}
</div>
${this.state.chats.map(chat =>
html`<${ChatListItem}
photo=${chat.photo}
active=${chat.id === activeChat}
key=${chat.id}
chat=${chat}/>`
)
}
<${ScrollViewport}>
${this.state.chats.map(chat =>
html`<${ChatListItem}
photo=${chat.photo}
active=${chat.id === activeChat}
key=${chat.id}
chat=${chat}/>`
)
}
</${ScrollViewport}>
</div>
</section>`
}

View File

@ -0,0 +1,101 @@
import { h, Component } from './preact.js';
import {html} from '../Helpers.js';
const EVENT_OPTS = {
passive: true,
capture: true
};
/** Virtual list, renders only visible items.
* @param {Number} rowHeight Use a static height value for each row. Prevents relayout.
* @param {Number} defaultRowHeight Initial row height, used prior to height being calculated from first item
* @param {Number} [overscan=10] Amount of rows to render above and below visible area of the list
* @param {Boolean} [sync=false] true forces synchronous rendering
* @example
* <ScrollViewport
* rowHeight={22}
* defaultRowHeight={22}
* sync
* />
*/
export default class ScrollViewport extends Component {
resized = () => {
let height = window.innerHeight || document.documentElement.offsetHeight;
if (height!==this.state.height) {
this.setState({ height });
}
};
scrolled = () => {
let offset = Math.max(0, this.base && -this.base.getBoundingClientRect().top || 0);
this.setState({ offset });
if (this.props.sync) this.forceUpdate();
};
computeRowHeight() {
if (this._height) return this._height;
let first = this.base && this.base.firstElementChild && this.base.firstElementChild.firstElementChild;
return this._height = (first && first.offsetHeight || 0);
}
componentDidUpdate() {
this.resized();
}
componentDidMount() {
this.resized();
this.scrolled();
addEventListener('resize', this.resized, EVENT_OPTS);
addEventListener('scroll', this.scrolled, EVENT_OPTS);
}
componentWillUnmount() {
removeEventListener('resize', this.resized, EVENT_OPTS);
removeEventListener('scroll', this.scrolled, EVENT_OPTS);
}
render({ overscan=10, rowHeight, defaultRowHeight, children, ...props }, { offset=0, height=0 }) {
rowHeight = rowHeight || this.computeRowHeight() || defaultRowHeight || 100;
// compute estimated height based on first item height and number of items:
let estimatedHeight = rowHeight * children.length;
if (typeof props.style==='string') {
props.style += ' height:'+estimatedHeight+'px;';
}
else {
(props.style || (props.style={})).height = estimatedHeight.toExponential() + 'px';
}
let start = 0,
visibleRowCount = 1;
if (rowHeight) {
// first visible row index
start = (offset / rowHeight)|0;
// actual number of visible rows (without overscan)
visibleRowCount = (height / rowHeight)|0;
// Overscan: render blocks of rows modulo an overscan row count
// This dramatically reduces DOM writes during scrolling
if (overscan) {
start = Math.max(0, start - (start % overscan));
visibleRowCount += overscan;
}
}
// last visible + overscan row index
let end = start + 1 + visibleRowCount;
// children currently in viewport plus overscan items
let visible = children.slice(start, end);
return html`
<div ${{...props}}>
<div style=${{ position: 'relative', top: start*rowHeight }}>
${visible}
</div>
</div>
`;
}
}

View File

@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("preact")):"function"==typeof define&&define.amd?define(["preact"],t):e.ScrollViewport=t(e.preact)}(this,function(e){function t(e,t){var o={};for(var i in e)t.indexOf(i)>=0||Object.prototype.hasOwnProperty.call(e,i)&&(o[i]=e[i]);return o}function o(e,t){if(e)return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){"function"!=typeof t&&null!==t||(e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t))}var n={passive:!0,capture:!0};return function(r){function s(){for(var e,t,i,n=arguments.length,s=Array(n),c=0;c<n;c++)s[c]=arguments[c];return e=t=o(this,r.call.apply(r,[this].concat(s))),t.resized=function(){var e=window.innerHeight||document.documentElement.offsetHeight;e!==t.state.height&&t.setState({height:e})},t.scrolled=function(){var e=Math.max(0,t.base&&-t.base.getBoundingClientRect().top||0);t.setState({offset:e}),t.props.sync&&t.forceUpdate()},i=e,o(t,i)}return i(s,r),s.prototype.computeRowHeight=function(){if(this._height)return this._height;var e=this.base&&this.base.firstElementChild&&this.base.firstElementChild.firstElementChild;return this._height=e&&e.offsetHeight||0},s.prototype.componentDidUpdate=function(){this.resized()},s.prototype.componentDidMount=function(){this.resized(),this.scrolled(),addEventListener("resize",this.resized,n),addEventListener("scroll",this.scrolled,n)},s.prototype.componentWillUnmount=function(){removeEventListener("resize",this.resized,n),removeEventListener("scroll",this.scrolled,n)},s.prototype.render=function(o,i){var n=i.offset,r=void 0===n?0:n,s=i.height,c=void 0===s?0:s,h=o.overscan,p=void 0===h?10:h,l=o.rowHeight,a=o.defaultRowHeight,f=o.children,u=t(o,["overscan","rowHeight","defaultRowHeight","children"]);l=l||this.computeRowHeight()||a||100;var d=l*f.length;"string"==typeof u.style?u.style+=" height:"+d+"px;":(u.style||(u.style={})).height=d.toExponential()+"px";var v=0,y=1;l&&(v=r/l|0,y=c/l|0,p&&(v=Math.max(0,v-v%p),y+=p));var g=v+1+y,m=f.slice(v,g);return e.h("div",u,e.h("div",{style:{position:"relative",top:v*l}},m))},s}(e.Component)});
//# sourceMappingURL=preact-scroll-viewport.min.js.map