Increase perceived performance by scrolling to the right spot before Hydrogen loads (#128)

This commit is contained in:
Eric Eastwood 2022-11-09 18:57:33 -06:00 committed by GitHub
parent 3671da0405
commit fa4720af04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 13 deletions

View File

@ -16,9 +16,7 @@ const vm = require('vm');
const path = require('path'); const path = require('path');
const { readFile } = require('fs').promises; const { readFile } = require('fs').promises;
const crypto = require('crypto'); const crypto = require('crypto');
const { parseHTML } = require('linkedom'); const { parseHTML } = require('linkedom');
const safeJson = require('../lib/safe-json');
// Setup the DOM context with any necessary shims/polyfills and ensure the VM // Setup the DOM context with any necessary shims/polyfills and ensure the VM
// context global has everything that a normal document does so Hydrogen can // context global has everything that a normal document does so Hydrogen can
@ -67,24 +65,36 @@ async function _renderHydrogenToStringUnsafe(renderOptions) {
assert(renderOptions.vmRenderScriptFilePath); assert(renderOptions.vmRenderScriptFilePath);
assert(renderOptions.vmRenderContext); assert(renderOptions.vmRenderContext);
assert(renderOptions.pageOptions); assert(renderOptions.pageOptions);
assert(renderOptions.pageOptions.locationHref);
assert(renderOptions.pageOptions.cspNonce); assert(renderOptions.pageOptions.cspNonce);
const { dom, vmContext } = createDomAndSetupVmContext(); const { dom, vmContext } = createDomAndSetupVmContext();
// A small `window.location` stub
if (!dom.window.location) {
const locationUrl = new URL(renderOptions.pageOptions.locationHref);
dom.window.location = {};
[
'hash',
'host',
'hostname',
'href',
'origin',
'password',
'pathname',
'port',
'protocol',
'search',
'username',
].forEach((key) => {
dom.window.location[key] = locationUrl[key];
});
}
// Define this for the SSR context // Define this for the SSR context
dom.window.matrixPublicArchiveContext = { dom.window.matrixPublicArchiveContext = {
...renderOptions.vmRenderContext, ...renderOptions.vmRenderContext,
}; };
// Serialize it for when we run this again client-side
const serializedContext = JSON.stringify(dom.window.matrixPublicArchiveContext);
dom.document.body.insertAdjacentHTML(
'beforeend',
`
<script type="text/javascript" nonce="${renderOptions.pageOptions.cspNonce}">
window.matrixPublicArchiveContext = ${safeJson(serializedContext)}
</script>
`
);
const vmRenderScriptFilePath = renderOptions.vmRenderScriptFilePath; const vmRenderScriptFilePath = renderOptions.vmRenderScriptFilePath;
const hydrogenRenderScriptCode = await readFile(vmRenderScriptFilePath, 'utf8'); const hydrogenRenderScriptCode = await readFile(vmRenderScriptFilePath, 'utf8');

View File

@ -26,6 +26,12 @@ async function renderHydrogenVmRenderScriptToPageHtml(
pageOptions, pageOptions,
}); });
// Serialize the state for when we run the Hydrogen render again client-side to
// re-hydrate the DOM
const serializedMatrixPublicArchiveContext = JSON.stringify({
...vmRenderContext,
});
const serializableSpans = getSerializableSpans(); const serializableSpans = getSerializableSpans();
const serializedSpans = JSON.stringify(serializableSpans); const serializedSpans = JSON.stringify(serializableSpans);
@ -51,6 +57,34 @@ async function renderHydrogenVmRenderScriptToPageHtml(
</head> </head>
<body> <body>
${hydrogenHtmlOutput} ${hydrogenHtmlOutput}
${
/**
* This inline snippet is used in to scroll the Hydrogen timeline to the
* right place immediately when the page loads instead of waiting for
* Hydrogen to load, hydrate and finally scroll.
*/ ''
}
<script type="text/javascript" nonce="${pageOptions.cspNonce}">
const qs = new URLSearchParams(window?.location?.search);
const atEventId = qs.get('at');
if (atEventId) {
const el = document.querySelector(\`[data-event-id="\${atEventId}"]\`);
requestAnimationFrame(() => {
el && el.scrollIntoView({ block: 'center' });
});
} else {
const el = document.querySelector('.js-bottom-scroll-anchor');
requestAnimationFrame(() => {
el && el.scrollIntoView({ block: 'end' });
});
}
</script>
<script type="text/javascript" nonce="${pageOptions.cspNonce}">
window.matrixPublicArchiveContext = ${safeJson(serializedMatrixPublicArchiveContext)}
</script>
${pageOptions.scripts ${pageOptions.scripts
.map( .map(
(scriptUrl) => (scriptUrl) =>

View File

@ -93,6 +93,7 @@ router.get(
title: `Matrix Public Archive`, title: `Matrix Public Archive`,
styles: [hydrogenStylesUrl, stylesUrl, roomDirectoryStylesUrl], styles: [hydrogenStylesUrl, stylesUrl, roomDirectoryStylesUrl],
scripts: [jsBundleUrl], scripts: [jsBundleUrl],
locationHref: urlJoin(basePath, req.originalUrl),
shouldIndex, shouldIndex,
cspNonce: res.locals.cspNonce, cspNonce: res.locals.cspNonce,
} }

View File

@ -420,6 +420,7 @@ router.get(
title: `${roomData.name} - Matrix Public Archive`, title: `${roomData.name} - Matrix Public Archive`,
styles: [hydrogenStylesUrl, stylesUrl], styles: [hydrogenStylesUrl, stylesUrl],
scripts: [jsBundleUrl], scripts: [jsBundleUrl],
locationHref: urlJoin(basePath, req.originalUrl),
shouldIndex, shouldIndex,
cspNonce: res.locals.cspNonce, cspNonce: res.locals.cspNonce,
} }

View File

@ -23,7 +23,12 @@ class JumpToNextActivitySummaryTileView extends TemplateView {
return t.div( return t.div(
{ {
className: 'JumpToNextActivitySummaryTileView', className: {
JumpToNextActivitySummaryTileView: true,
// Used by page loaded JavaScript to quickly jump the scroll viewport down
// while we wait for the rest of the JavaScript to load.
'js-bottom-scroll-anchor': true,
},
'data-event-id': vm.eventId, 'data-event-id': vm.eventId,
}, },
[ [