From 6eda27b7cf7b37f9f483f1ea96470ae5ee5425ad Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 24 Apr 2018 16:40:23 -0700 Subject: [PATCH] report: score gauges, metrics display, add rich tooltips (#5009) --- .../report/v2/renderer/category-renderer.js | 27 ++- .../renderer/performance-category-renderer.js | 54 +++--- .../report/v2/renderer/report-renderer.js | 3 + lighthouse-core/report/v2/report-styles.css | 170 +++++++++++++----- lighthouse-core/report/v2/templates.html | 132 +++++++------- .../v2/renderer/category-renderer-test.js | 3 +- .../performance-category-renderer-test.js | 2 +- lighthouse-extension/test/extension-test.js | 4 +- 8 files changed, 237 insertions(+), 158 deletions(-) diff --git a/lighthouse-core/report/v2/renderer/category-renderer.js b/lighthouse-core/report/v2/renderer/category-renderer.js index 3309adb7b7fb..f9b11945af4e 100644 --- a/lighthouse-core/report/v2/renderer/category-renderer.js +++ b/lighthouse-core/report/v2/renderer/category-renderer.js @@ -235,26 +235,19 @@ class CategoryRenderer { */ renderScoreGauge(category) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge', this.templateContext); - this.dom.find('.lh-gauge__wrapper', tmpl).href = `#${category.id}`; - this.dom.find('.lh-gauge__label', tmpl).textContent = category.name; - - const scoreOutOf100 = Math.round(category.score * 100); - const fillRotation = Math.floor((scoreOutOf100 / 100) * 180); + const wrapper = this.dom.find('.lh-gauge__wrapper', tmpl); + wrapper.href = `#${category.id}`; + wrapper.classList.add(`lh-gauge__wrapper--${Util.calculateRating(category.score)}`); const gauge = this.dom.find('.lh-gauge', tmpl); - gauge.setAttribute('data-progress', scoreOutOf100); // .dataset not supported in jsdom. - gauge.classList.add(`lh-gauge--${Util.calculateRating(category.score)}`); - - this.dom.findAll('.lh-gauge__fill', gauge).forEach(el => { - el.style.transform = `rotate(${fillRotation}deg)`; - }); - - this.dom.find('.lh-gauge__mask--full', gauge).style.transform = - `rotate(${fillRotation}deg)`; - this.dom.find('.lh-gauge__fill--fix', gauge).style.transform = - `rotate(${fillRotation * 2}deg)`; - this.dom.find('.lh-gauge__percentage', gauge).textContent = scoreOutOf100; + // 329 is ~= 2 * Math.PI * gauge radius (53) + // https://codepen.io/xgad/post/svg-radial-progress-meters + // score of 50: `stroke-dasharray: 164.5 329`; + this.dom.find('.lh-gauge-arc', gauge).style.strokeDasharray = `${category.score * 329} 329`; + const scoreOutOf100 = Math.round(category.score * 100); + this.dom.find('.lh-gauge__percentage', tmpl).textContent = scoreOutOf100; + this.dom.find('.lh-gauge__label', tmpl).textContent = category.name; return tmpl; } diff --git a/lighthouse-core/report/v2/renderer/performance-category-renderer.js b/lighthouse-core/report/v2/renderer/performance-category-renderer.js index 064f460b604a..bb90228a4283 100644 --- a/lighthouse-core/report/v2/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/v2/renderer/performance-category-renderer.js @@ -10,21 +10,20 @@ class PerformanceCategoryRenderer extends CategoryRenderer { /** * @param {!ReportRenderer.AuditJSON} audit - * @param {number} scale * @return {!Element} */ - _renderTimelineMetricAudit(audit, scale) { - const tmpl = this.dom.cloneTemplate('#tmpl-lh-timeline-metric', this.templateContext); - const element = this.dom.find('.lh-timeline-metric', tmpl); - element.classList.add(`lh-timeline-metric--${Util.calculateRating(audit.result.score)}`); + _renderMetric(audit) { + const tmpl = this.dom.cloneTemplate('#tmpl-lh-perf-metric', this.templateContext); + const element = this.dom.find('.lh-perf-metric', tmpl); + element.classList.add(`lh-perf-metric--${Util.calculateRating(audit.result.score)}`); - const titleEl = this.dom.find('.lh-timeline-metric__title', tmpl); + const titleEl = this.dom.find('.lh-perf-metric__title', tmpl); titleEl.textContent = audit.result.description; - const valueEl = this.dom.find('.lh-timeline-metric__value', tmpl); + const valueEl = this.dom.find('.lh-perf-metric__value span', tmpl); valueEl.textContent = audit.result.displayValue; - const descriptionEl = this.dom.find('.lh-timeline-metric__description', tmpl); + const descriptionEl = this.dom.find('.lh-perf-metric__description', tmpl); descriptionEl.appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.helpText)); if (typeof audit.result.rawValue !== 'number') { @@ -33,9 +32,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer { return element; } - const sparklineBarEl = this.dom.find('.lh-sparkline__bar', tmpl); - sparklineBarEl.style.width = `${audit.result.rawValue / scale * 100}%`; - return element; } @@ -119,38 +115,37 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const metricAudits = category.audits.filter(audit => audit.group === 'perf-metric'); const metricAuditsEl = this.renderAuditGroup(groups['perf-metric'], {expandable: false}); - const timelineContainerEl = this.dom.createChildOf(metricAuditsEl, 'div', - 'lh-timeline-container'); - const timelineEl = this.dom.createChildOf(timelineContainerEl, 'div', 'lh-timeline'); - - let perfTimelineScale = 0; - metricAudits.forEach(audit => { - if (typeof audit.result.rawValue === 'number' && audit.result.rawValue) { - perfTimelineScale = Math.max(perfTimelineScale, audit.result.rawValue); - } + + // Metrics + const keyMetrics = metricAudits.filter(a => a.weight >= 3); + const otherMetrics = metricAudits.filter(a => a.weight < 3); + + const metricsBoxesEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-metrics-container'); + const metricsColumn1El = this.dom.createChildOf(metricsBoxesEl, 'div', 'lh-metrics-column'); + const metricsColumn2El = this.dom.createChildOf(metricsBoxesEl, 'div', 'lh-metrics-column'); + + keyMetrics.forEach(item => { + metricsColumn1El.appendChild(this._renderMetric(item)); + }); + otherMetrics.forEach(item => { + metricsColumn2El.appendChild(this._renderMetric(item)); }); + // Filmstrip + const timelineEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-timeline'); const thumbnailAudit = category.audits.find(audit => audit.id === 'screenshot-thumbnails'); const thumbnailResult = thumbnailAudit && thumbnailAudit.result; if (thumbnailResult && thumbnailResult.details) { const thumbnailDetails = /** @type {!DetailsRenderer.FilmstripDetails} */ (thumbnailResult.details); - perfTimelineScale = Math.max(perfTimelineScale, thumbnailDetails.scale); const filmstripEl = this.detailsRenderer.render(thumbnailDetails); timelineEl.appendChild(filmstripEl); } - metricAudits.forEach(item => { - if (item.id === 'speed-index' || item.id === 'estimated-input-latency') { - return metricAuditsEl.appendChild(this.renderAudit(item)); - } - - timelineEl.appendChild(this._renderTimelineMetricAudit(item, perfTimelineScale)); - }); - metricAuditsEl.open = true; element.appendChild(metricAuditsEl); + // Opportunities const hintAudits = category.audits .filter(audit => audit.group === 'perf-hint' && audit.result.score < 1) .sort((auditA, auditB) => auditB.result.rawValue - auditA.result.rawValue); @@ -163,6 +158,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { element.appendChild(hintAuditsEl); } + // Diagnostics const infoAudits = category.audits .filter(audit => audit.group === 'perf-info' && audit.result.score < 1); if (infoAudits.length) { diff --git a/lighthouse-core/report/v2/renderer/report-renderer.js b/lighthouse-core/report/v2/renderer/report-renderer.js index aad456468130..524c0d37fe29 100644 --- a/lighthouse-core/report/v2/renderer/report-renderer.js +++ b/lighthouse-core/report/v2/renderer/report-renderer.js @@ -171,6 +171,9 @@ class ReportRenderer { categories.appendChild(renderer.render(category, report.reportGroups)); } + const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext); + scoreHeader.appendChild(scoreScale); + reportSection.appendChild(this._renderReportFooter(report)); return container; diff --git a/lighthouse-core/report/v2/report-styles.css b/lighthouse-core/report/v2/report-styles.css index cde7b628d4a0..465c6ab2d171 100644 --- a/lighthouse-core/report/v2/report-styles.css +++ b/lighthouse-core/report/v2/report-styles.css @@ -25,11 +25,11 @@ --expandable-indent: 20px; --secondary-text-color: #565656; /*--accent-color: #3879d9;*/ - --fail-color: #df332f; - --pass-color: #2b882f; + --fail-color: hsl(1, 73%, 45%); + --average-color: hsl(31, 100%, 45%); /* md orange 800 */ + --pass-color: hsl(139, 70%, 30%); --informative-color: #0c50c7; - --manual-color: #757575; - --average-color: #ef6c00; /* md orange 800 */ + --medium-75-gray: #757575; --warning-color: #ffab00; /* md amber a700 */ --report-border-color: #ccc; --report-secondary-border-color: #ebebeb; @@ -174,7 +174,7 @@ summary { word-break: break-word; color: hsl(174, 100%, 27%); } -span.lh-node:hover { +.lh-node:hover { background: hsl(0, 0%, 98%); border-radius: 2px; } @@ -221,7 +221,7 @@ span.lh-node:hover { .lh-score--manual .lh-score__value::after { background: url('data:image/svg+xml;utf8,manual') no-repeat 50% 50%; background-size: 18px; - background-color: var(--manual-color); + background-color: var(--medium-75-gray); width: 20px; height: 20px; position: relative; @@ -362,78 +362,82 @@ span.lh-node:hover { /* Perf Timeline Metric */ -.lh-timeline-metric { +.lh-metrics-container { + display: flex; +} + +.lh-metrics-column { + flex: 1; + margin-right: 20px; +} + +.lh-perf-metric { position: relative; - margin-bottom: calc(2 * var(--lh-audit-vpadding)); - padding-top: var(--lh-audit-vpadding); + display: flex; + justify-content: space-between; + + padding: calc(2 * var(--lh-audit-vpadding)) 0; border-top: 1px solid var(--report-secondary-border-color); } -.lh-timeline-metric__header { +.lh-perf-metric__header { display: flex; } -.lh-timeline-metric__details { +.lh-perf-metric__details { order: -1; } -.lh-timeline-metric__title { +.lh-perf-metric__title { font-size: var(--body-font-size); line-height: var(--body-line-height); display: flex; } -.lh-timeline-metric__name { +.lh-perf-metric__name { flex: 1; } -.lh-timeline-metric__description { +.lh-perf-metric__description { color: var(--secondary-text-color); } -.lh-timeline-metric__value { - width: var(--lh-audit-score-width); - text-align: right; -} -.lh-timeline-metric--pass .lh-timeline-metric__value { +.lh-perf-metric--pass .lh-perf-metric__value { color: var(--pass-color); } -.lh-timeline-metric--average .lh-timeline-metric__value { - color: var(--average-color); +.lh-perf-metric .lh-perf-metric__value span::after { + content: ''; + width: var(--body-font-size); + height: var(--body-font-size); + background-size: contain; + display: inline-block; + vertical-align: text-bottom; + margin-left: calc(var(--body-font-size) / 2); } -.lh-timeline-metric--fail .lh-timeline-metric__value { - color: var(--fail-color); +.lh-perf-metric--pass .lh-perf-metric__value span::after { + background: url('data:image/svg+xml;utf8,check') no-repeat 50% 50%; } -.lh-timeline-metric__sparkline { - position: absolute; - left: 0; - right: 0; - top: -1px; - height: 3px; - width: 100%; -} -.lh-timeline-metric__sparkline .lh-sparkline__bar { - float: none; +.lh-perf-metric--average .lh-perf-metric__value { + color: var(--average-color); } - -.lh-timeline-metric--pass .lh-sparkline__bar { - background: var(--pass-color); +.lh-perf-metric--average .lh-perf-metric__value span::after { + background: url('data:image/svg+xml;utf8,info') no-repeat 50% 50%; } -.lh-timeline-metric--average .lh-sparkline__bar { - background: var(--average-color); -} -.lh-timeline-metric--fail .lh-sparkline__bar { - background: var(--fail-color); +.lh-perf-metric--fail .lh-perf-metric__value { + color: var(--fail-color); +} +.lh-perf-metric--fail .lh-perf-metric__value span::after { + background: url('data:image/svg+xml;utf8,warn') no-repeat 50% 50%; } -.lh-timeline-metric .lh-debug { +.lh-perf-metric .lh-debug { margin-left: var(--expandable-indent); } @@ -583,7 +587,7 @@ span.lh-node:hover { } /* correlate metric end location with sparkline */ -.lh-timeline-metric:hover .lh-sparkline__bar::after { +.lh-perf-metric:hover .lh-sparkline__bar::after { content: ''; height: 100vh; width: 2px; @@ -722,14 +726,48 @@ span.lh-node:hover { display: flex; justify-content: center; overflow-x: hidden; - padding: var(--section-padding); + padding: var(--section-padding) var(--section-padding) calc(var(--section-padding) * 3); border-bottom: 1px solid var(--report-border-color); + position: relative; } .lh-scores-header__solo { padding: 0; border: 0; } +.lh-scorescale { + position: absolute; + right: var(--section-padding); + bottom: var(--section-padding); + color: var(--medium-75-gray); +} + +.lh-scorescale-range { + margin-left: 10px; +} + +.lh-scorescale-range::before { + content: ''; + width: var(--body-font-size); + height: calc(var(--body-font-size) * .60); + border-radius: 4px; + display: inline-block; + margin: 0 5px; +} + +.lh-scorescale-range--pass::before { + background-color: var(--pass-color); +} + +.lh-scorescale-range--average::before { + background-color: var(--average-color); +} + +.lh-scorescale-range--fail::before { + background-color: var(--fail-color); +} + + .lh-categories { width: 100%; overflow: hidden; @@ -917,4 +955,48 @@ summary.lh-passed-audits-summary { object-fit: contain; } + +/* Tooltip */ +.tooltip-boundary { + position: relative; +} + +.tooltip { + position: absolute; + display: none; /* Don't retain these layers when not needed */ + opacity: 0; +} + +.tooltip-boundary:hover .tooltip { + display: block; + animation: fadeInTooltip 150ms; + animation-fill-mode: forwards; + animation-delay: 500ms; + min-width: 15em; + background: #ffffff; + padding: 15px; + border-radius: 5px; + bottom: 100%; + z-index: 1; + will-change: opacity; +} + +.tooltip::before { + content: ""; + border: solid transparent; + border-bottom-color: #fff; + border-width: 10px; + position: absolute; + bottom: -20px; + transform: rotate(180deg); + pointer-events: none; +} + +@keyframes fadeInTooltip { + 0% { opacity: 0; } + 75% { opacity: 1; } + 100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); } +} + + /*# sourceURL=report.styles.css */ diff --git a/lighthouse-core/report/v2/templates.html b/lighthouse-core/report/v2/templates.html index 640d50d3b286..93247b33517c 100644 --- a/lighthouse-core/report/v2/templates.html +++ b/lighthouse-core/report/v2/templates.html @@ -6,6 +6,16 @@ + + + - -