Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit da34cc9

Browse files
authored
feat(demos): Add global demoReady() function (#1919)
For this PR, I only updated the button demo page to use the new `demoReady()` function. #1920 updates the remaining demo pages. Also note that our packaged JS is now being loaded with `async`, which speeds up initial rendering time over slow connections. Using "Mid-tier mobile" network emulation in Chrome devtools, the [TTFMP](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint) of the button demo page was reduced from ~10.5s to ~9.5s, and the time to render the Material icon font dropped from ~12s to ~10.5s.
1 parent aeed3bd commit da34cc9

File tree

3 files changed

+115
-20
lines changed

3 files changed

+115
-20
lines changed

demos/button.html

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
2525
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
2626
<link rel="stylesheet" href="/assets/button.css">
27+
<script src="/ready.js"></script>
2728
<style>
2829
.demo-wrapper {
2930
padding-bottom: 100px;
@@ -75,7 +76,6 @@
7576
left: 36px;
7677
color: rgba(33, 33, 33, .38);
7778
}
78-
7979
</style>
8080
</head>
8181
<body class="mdc-typography">
@@ -356,25 +356,12 @@ <h1 class="mdc-typography--display2">CSS Only</h1>
356356
</section>
357357
</main>
358358

359-
<script src="/assets/material-components-web.js"></script>
359+
<script src="/assets/material-components-web.js" async></script>
360360
<script>
361-
// Because we load our CSS via webpack, we need to ensure that all of the correct styles
362-
// are applied before ripples are attached. Otherwise, ripples may use the computed styles of
363-
// elements before our CSS is applied, leading to improper UX.
364-
(function() {
365-
var pollId = 0;
366-
pollId = setInterval(function() {
367-
var pos = getComputedStyle(document.querySelector('.mdc-button')).position;
368-
if (pos === 'relative') {
369-
init();
370-
clearInterval(pollId);
371-
}
372-
}, 250);
373-
function init() {
374-
var btns = document.querySelectorAll('.mdc-button:not([data-demo-no-js])');
375-
for (var i = 0, btn; btn = btns[i]; i++) {
376-
mdc.ripple.MDCRipple.attachTo(btn);
377-
}
361+
demoReady(function() {
362+
var btns = document.querySelectorAll('.mdc-button:not([data-demo-no-js])');
363+
for (var i = 0, btn; btn = btns[i]; i++) {
364+
mdc.ripple.MDCRipple.attachTo(btn);
378365
}
379366

380367
var demoWrapper = document.querySelector('.demo-wrapper');
@@ -393,7 +380,7 @@ <h1 class="mdc-typography--display2">CSS Only</h1>
393380
});
394381

395382
});
396-
})();
383+
});
397384
</script>
398385
</body>
399386
</html>

demos/common.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ $mdc-theme-secondary: #018786 !default; // baseline teal, 600 tone
3636

3737
$dark-button-color: $material-color-light-green-a200;
3838

39+
// Used by ready.js to test whether the CSS has finished loading.
40+
.demo-ready-detect {
41+
position: relative;
42+
}
43+
3944
fieldset {
4045
margin: 0;
4146
padding: 0;

demos/ready.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* @license
3+
* Copyright 2017 Google Inc. All Rights Reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
// eslint-disable no-unused-vars no-var
19+
20+
/**
21+
* Adds the given event handler to the queue. It will be executed asynchronously after all external JS and CSS resources
22+
* have finished loading (as determined by continuous long-polling with a timeout). If this function is called after all
23+
* resources have finished loading, the given handler function will be invoked synchronously (in the same call stack).
24+
* Handlers are invoked in FIFO order.
25+
* @param {function() : undefined} handler
26+
*/
27+
window.demoReady = (function() {
28+
var TIMEOUT_MS = 60 * 1000;
29+
var DELAY_MS = 100;
30+
31+
var isReadyCached = false;
32+
var handlers = [];
33+
var testDom = null;
34+
var startTimeMs = null;
35+
var timer = null;
36+
37+
function isReady() {
38+
if (isReadyCached) {
39+
return true;
40+
}
41+
ensureDetectionDom();
42+
isReadyCached = Boolean(window.mdc) && getComputedStyle(testDom).position === 'relative';
43+
return isReadyCached;
44+
}
45+
46+
function ensureDetectionDom() {
47+
if (testDom) {
48+
return;
49+
}
50+
testDom = document.createElement('div');
51+
testDom.classList.add('demo-ready-detect');
52+
document.body.appendChild(testDom);
53+
}
54+
55+
function removeDetectionDom() {
56+
if (!testDom) {
57+
return;
58+
}
59+
document.body.removeChild(testDom);
60+
testDom = null;
61+
}
62+
63+
function startTimer() {
64+
if (timer) {
65+
return;
66+
}
67+
startTimeMs = Date.now();
68+
timer = setInterval(tick, DELAY_MS);
69+
}
70+
71+
function tick() {
72+
if (isReady()) {
73+
clearInterval(timer);
74+
removeDetectionDom();
75+
invokeHandlers();
76+
return;
77+
}
78+
79+
const elapsedTimeMs = Date.now() - startTimeMs;
80+
if (elapsedTimeMs > TIMEOUT_MS) {
81+
clearInterval(timer);
82+
removeDetectionDom();
83+
console.error('Timed out waiting for JS and CSS to load');
84+
return;
85+
}
86+
}
87+
88+
function invokeHandlers() {
89+
handlers.forEach(function(handler) {
90+
handler();
91+
});
92+
}
93+
94+
return function addHandler(handler) {
95+
if (isReady()) {
96+
handler();
97+
return;
98+
}
99+
100+
handlers.push(handler);
101+
startTimer();
102+
};
103+
})();

0 commit comments

Comments
 (0)