-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
report(redesign): add sticky scores header #8524
Changes from 10 commits
9f3268e
9552d04
13467f1
198441d
e271f89
039c2a9
06fe7b8
02c8970
2a4c356
7455ec4
da82714
a379435
fe023ee
e91c833
4a3159c
c99fbd7
0613ab7
1c369d1
23c5c2f
4377ec4
1179d51
557db06
365f5e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,10 +247,25 @@ class ReportRenderer { | |
reportSection.appendChild(this._renderReportFooter(report)); | ||
|
||
const reportFragment = this._dom.createFragment(); | ||
|
||
if (!this._dom.isDevTools()) { | ||
const topbarDocumentFragment = this._renderReportTopbar(report); | ||
reportFragment.appendChild(topbarDocumentFragment); | ||
} | ||
|
||
if (scoreHeader && !this._dom.isDevTools()) { | ||
const stickyHeader = this._dom.createElement('div', 'lh-sticky-header'); | ||
this._dom.createChildOf(stickyHeader, 'div', 'lh-highlighter'); | ||
|
||
// The sticky header is just the score gauges, but styled to be smaller. Just | ||
// clone the gauges from the score header. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not cloned anymore |
||
for (const gaugeWrapperEl of this._dom.findAll('.lh-gauge__wrapper', scoreHeader)) { | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this feels pretty squicky to me :) Can we make a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
stickyHeader.appendChild(gaugeWrapperEl.cloneNode(true)); | ||
} | ||
|
||
reportFragment.appendChild(stickyHeader); | ||
} | ||
|
||
reportFragment.appendChild(headerContainer); | ||
reportFragment.appendChild(container); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ class ReportUIFeatures { | |
this.onKeyDown = this.onKeyDown.bind(this); | ||
this.printShortCutDetect = this.printShortCutDetect.bind(this); | ||
this.onChevronClick = this.onChevronClick.bind(this); | ||
this._handleStickyHeader = this._handleStickyHeader.bind(this); | ||
} | ||
|
||
/** | ||
|
@@ -65,6 +66,11 @@ class ReportUIFeatures { | |
this._resetUIState(); | ||
this._document.addEventListener('keydown', this.printShortCutDetect); | ||
this._document.addEventListener('copy', this.onCopy); | ||
this._document.addEventListener('scroll', this._handleStickyHeader); | ||
// window.addEventListener is undefined in jest tests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which test file(s) fails? This sounds like jsdom wasn't set up correctly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup that was it, fixed. |
||
if (window.addEventListener) { | ||
window.addEventListener('resize', this._handleStickyHeader); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -380,6 +386,42 @@ class ReportUIFeatures { | |
this._document.body.removeChild(a); | ||
setTimeout(_ => URL.revokeObjectURL(href), 500); | ||
} | ||
|
||
_handleStickyHeader() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe rename to make it clear this is for adjusting the sticky header on scroll events? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It handles sticky header visibility, and the highlighting. It also fires on scroll and resize.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haha, I was thinking more like Arguably (it's a stretch :) resize is a special case of scrolling for this element, resizing is uncommon compared to scrolling, and the main thing is the name can communicate something about what it's up to and when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sold, to |
||
const topbarEl = this._document.querySelector('.lh-topbar'); | ||
const scoreScaleEl = this._document.querySelector('.lh-scorescale'); | ||
const stickyHeaderEl = this._document.querySelector('.lh-sticky-header'); | ||
const highlightEl = this._document.querySelector('.lh-highlighter'); | ||
if (!topbarEl || !scoreScaleEl || !stickyHeaderEl || !highlightEl) return; | ||
|
||
// Show sticky header when the score scale begins to go underneath the topbar. | ||
const showStickyHeader = | ||
topbarEl.getBoundingClientRect().bottom - scoreScaleEl.getBoundingClientRect().top >= 0; | ||
stickyHeaderEl.classList.toggle('lh-sticky-header--stuck', showStickyHeader); | ||
|
||
// Highlight mini gauge when section is in view. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. early return if |
||
// Use the middle of the viewport as an anchor - the closest category to the middle | ||
// is the one "in view". | ||
let highlightIndex = 0; | ||
let highlightIndexDistance = Number.POSITIVE_INFINITY; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just curious, why not |
||
const categoryEls = this._document.querySelectorAll('.lh-category'); | ||
for (const [index, categoryEl] of Object.entries(categoryEls)) { | ||
// Normalize to middle of viewport. | ||
const distanceToMiddle = categoryEl.getBoundingClientRect().top - window.innerHeight / 2; | ||
// Closest negative distance to zero wins. | ||
if (distanceToMiddle < 0 && highlightIndexDistance > distanceToMiddle) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like we could simplify some of this but maybe I'm misunderstanding
this seems equivalent to "we highlight the last category that starts above the middle of the window" which at least for me is a lot easier to understand. maybe something like the below? const categoriesAboveTheMiddle = categories.filter(el => el.getBoundingClientRect().top - window.innerHeight / 2)
const categoryToHighlight = categoriesAboveTheMiddle[categoriesAboveTheMiddle.length - 1]
const guageToHighlight = gauges[categories.indexOf(categoryToHighlight)] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this works: // Highlight mini gauge when section is in view.
// In view = the last category that starts above the middle of the window.
const categoryEls = Array.from(this._document.querySelectorAll('.lh-category'));
const categoriesAboveTheMiddle =
categoryEls.filter(el => el.getBoundingClientRect().top - window.innerHeight / 2 < 0);
const highlightIndex =
categoriesAboveTheMiddle.length > 0 ? categoriesAboveTheMiddle.length - 1 : 0;
// Category order matches gauge order in sticky header.
// TODO(hoten): not 100% true yet, need to order gauges like: core, pwa, plugins. Remove
// this comment when that is done.
const gaugeToHighlight = stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper')[highlightIndex];
// @ts-ignore
highlightEl.style.left = gaugeToHighlight.getBoundingClientRect().left + 'px'; thanks for making more declarative, i'll update |
||
highlightIndex = parseInt(index); | ||
highlightIndexDistance = -distanceToMiddle; | ||
} | ||
} | ||
|
||
// Category order matches gauge order in sticky header. | ||
// TODO(hoten): not 100% true yet, need to order gauges like: core, pwa, plugins. Remove | ||
// this comment when that is done. | ||
const gaugeToHighlight = stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper')[highlightIndex]; | ||
// @ts-ignore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what are we ignoring? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs to be HTMLElement |
||
highlightEl.style.left = gaugeToHighlight.getBoundingClientRect().left + 'px'; | ||
} | ||
} | ||
|
||
if (typeof module !== 'undefined' && module.exports) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,7 +80,7 @@ describe('ReportRenderer', () => { | |
assert.ok(output.querySelector('.lh-header-sticky'), 'has a header'); | ||
assert.ok(output.querySelector('.lh-report'), 'has report body'); | ||
assert.equal(output.querySelectorAll('.lh-gauge__wrapper, .lh-gauge--pwa__wrapper').length, | ||
sampleResults.reportCategories.length * 2, 'renders category gauges'); | ||
sampleResults.reportCategories.length * 3, 'renders category gauges'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add a comment for where this multiplier comes from |
||
// no fireworks | ||
assert.ok(output.querySelector('.score100') === null, 'has no fireworks treatment'); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need a test for this, though jsdom (AFAIK) doesn't support scrolling, so it will have to be a limited one. Since we have a nice complete "renders score gauges in this order" test, maybe just a test that
.lh-sticky-header
has child gauges that match the ones in.lh-scores-header
?Or something like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo that doesn't seem like a useful test, since these gauges are created with the same function.
jsdom/jsdom#1422 (comment) this makes me think it should be possible to fake it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok it'd be way too much faking, basically all the
getBoundingClientRect
needs to be mocked