- Part 1: Achieve a pagespeed insights score of above 90 on index.html of the portfolio page.
- Part 2: Optimize the function for resizing pissas on pizza.html to do so in less than 5ms.
- Part 3: Achieve a framerate of 60fps when scrolling in pizza.html.
- Part 4: Make this documentation.
After viewing the classroom lessons and reviewing the code, I broke this project down into the three parts above. Then I added this documentation to the end.
- I added print media query to print css to stop its render blocking.
- I added async to google analytics.
- I changed the size of the pizzeria picture to match how it's shown on index.html instead of shrinking such a huge image. I named that image pizzeriaMin.png. I also compressed the images using ImageOptim. I replaced Cam's picture with mine and optimized it also.
- I'm a newbie at gulp, so this part took me a little bit to figure out. I minified inlined the CSS. This part got me to where I needed to be as far as pagespeed insights.
- Original Code
function changePizzaSizes(size) {
for (var i = 0; i < document.querySelectorAll(".randomPizzaContainer").length; i++) {
var dx = determineDx(document.querySelectorAll(".randomPizzaContainer")[i], size);
var newwidth = (document.querySelectorAll(".randomPizzaContainer")[i].offsetWidth + dx) + 'px';
document.querySelectorAll(".randomPizzaContainer")[i].style.width = newwidth;
}
}
changePizzaSizes(size);
}
- First Changes
function changePizzaSizes(size) {
var dx = determineDx(document.getElementsByClassName("randomPizzaContainer")[1], size);
var newwidth = (document.getElementsByClassName("randomPizzaContainer")[1].offsetWidth + dx) + 'px';
// Made this new variable from what was once the start of the function
var pizzaContainerLength = document.getElementsByClassName("randomPizzaContainer").length;
/* ---------------------------------------------
I moved the for loop down here in the function,
so that it calculates the variables first
--------------------------------------------- */
for (var i = 0; i < pizzaContainerLength; i++) {
document.getElementsByClassName("randomPizzaContainer")[i].style.width = newwidth;
}
}
changePizzaSizes(size);
}
-
This got me to 2.44ms, well below 5ms. Then I noticed I could make it simpler:
-
More Changes
function changePizzaSizes(size) {
var dx = determineDx(document.getElementsByClassName("randomPizzaContainer")[1], size);
var newwidth = (document.getElementsByClassName("randomPizzaContainer")[1].offsetWidth + dx) + 'px';
// Made this new variable from what was once the start of the function
var pizzaElements = document.getElementsByClassName("randomPizzaContainer");
/* ---------------------------------------------
I moved the for loop down here in the function,
so that it calculates the variables first
--------------------------------------------- */
for (var i = 0; i < pizzaElements.length; i++) {
pizzaElements[i].style.width = newwidth;
}
}
changePizzaSizes(size);
}
- That small change brought me from an average 2.44ms to 2.25ms
Special thanks to mcs, forum mentor on Udacity's discussion forum. He really helped me get the ball rolling on this part of the project after I read this post. Also, thanks to for posting the office hours.
- I moved the DOM calculations out of the for-loop within updatePositions. Also, I changed out querySelectorAll for getElementsByClassName
- Original Code:
function updatePositions() {
frame++;
window.performance.mark("mark_start_frame");
var items = document.querySelectorAll('.mover');
for (var i = 0; i < items.length; i++) {
var phase = Math.sin((document.body.scrollTop / 1250) + (i % 5));
items[i].style.left = items[i].basicLeft + 100 * phase + 'px';
}
}
- What I did:
/* ----------------------------------------------------------------------
querySelectorAll is slower than getElementsByClassName, I switched them
--------------------------------------------------------------------- */
function updatePositions() {
frame++;
window.performance.mark("mark_start_frame");
/* --- Moved these variables out of the for-loop --- */
var scroll = document.body.scrollTop;
var items = document.getElementsByClassName('mover');
var itemsLength = items.length;
for (var i = 0; i < itemsLength; i++) {
var phase = Math.sin((scroll / 1250) + (i % 5));
items[i].style.left = items[i].basicLeft + 100 * phase + 'px';
}
}
- I moved styles out of the JavaScript and into the CSS .mover class
- Original code:
document.addEventListener('DOMContentLoaded', function() {
var cols = 8;
var s = 256;
for (var i = 0; i < 200; i++) {
var elem = document.createElement('img');
elem.className = 'mover';
elem.src = "images/pizza.png";
elem.style.height = "100px";
elem.style.width = "73.333px";
elem.basicLeft = (i % cols) * s;
elem.style.top = (Math.floor(i / cols) * s) + 'px';
document.querySelector("#movingPizzas1").appendChild(elem);
}
updatePositions();
}
- My Code in main.js:
/* --------------------------------------------------------------------------------
I moved the styles code for the sizing of the pizzas to the css to the .mover class
-------------------------------------------------------------------------------- */
/* - Generates the sliding pizzas when the page loads. */
document.addEventListener('DOMContentLoaded', function() {
var cols = 8;
var s = 256;
for (var i = 0; i < 200; i++) {
var elem = document.createElement('img');
elem.className = 'mover';
elem.src = "images/pizza.png";
elem.basicLeft = (i % cols) * s;
elem.style.top = (Math.floor(i / cols) * s) + 'px';
document.querySelector("#movingPizzas1").appendChild(elem);
}
updatePositions();
}
- style.css:
.mover {
position: fixed;
width: 73px;
z-index: -1;
}
- My results from those changes were good, but not good enough:
- My attempt to optimize things further in updatepositions:
function updatePositions() {
frame++;
window.performance.mark("mark_start_frame");
/* --- Moved these variables out of the for-loop --- */
var scroll = (document.body.scrollTop / 1250);
var items = document.getElementsByClassName('mover');
var itemsLength = items.length;
for (var i = 0; i < itemsLength; i++) {
var phase = Math.sin(scroll + (i % 5));
items[i].style.left = items[i].basicLeft + 100 * phase + 'px';
}
- And then in addEventListener('DOMContentLoaded')
document.addEventListener('DOMContentLoaded', function() {
var cols = 8;
var s = 256;
var rows = Math.floor(window.screen.height / s);
var elementNumber = cols * rows;
var elem = [];
var appendElementsHere = document.querySelector("#movingPizzas1");
for (var i = 0; i < elementNumber; i++) {
elem = document.createElement('img');
elem.className = 'mover';
elem.src = "images/pizza.png";
elem.basicLeft = (i % cols) * s;
elem.style.top = (Math.floor(i / cols) * s) + 'px';
appendElementsHere.appendChild(elem);
}
updatePositions();
});
Throwing requestAnimationFrame into the mix:
function updatePositions() {
animating = false;
frame++;
window.performance.mark("mark_start_frame");
/* --- Moved these variables out of the for-loop --- */
var scroll = (document.body.scrollTop / 1250);
var items = document.getElementsByClassName('mover');
var itemsLength = items.length;
for (var i = 0; i < itemsLength; i++) {
var phase = Math.sin(scroll + (i % 5));
items[i].style.left = items[i].basicLeft + 100 * phase + 'px';
}
window.addEventListener('scroll', rAf);
function rAf() {
if(!animating) {
requestAnimationFrame(updatePositions);
}
animating = true;
}
- & also going back to the styles within the script as they were before, except I corrected the perportions of the image
/* - Generates the sliding pizzas when the page loads. */
document.addEventListener('DOMContentLoaded', function() {
var cols = 8;
var s = 256;
var rows = Math.floor(window.screen.height / s);
var elementNumber = cols * rows;
var elem = [];
var appendElementsHere = document.querySelector("#movingPizzas1");
for (var i = 0; i < elementNumber; i++) {
elem = document.createElement('img');
elem.className = 'mover';
elem.src = "images/pizza.png";
elem.style.height = "100px";
elem.style.width = "77.333px"; // corrected the proportion of the pizza
elem.basicLeft = (i % cols) * s;
elem.style.top = (Math.floor(i / cols) * s) + 'px';
appendElementsHere.appendChild(elem);
}
updatePositions();
});
- So much for fixing all this with optimizations. Time to learn how to use Translate:
var items = []; // makes items a global value on DOMcontentLoaded
var itemsLength = []; // makes itemsLength global
function updatePositions() {
frame++;
window.performance.mark("mark_start_frame");
/* --- Moved these variables out of the for-loop --- */
var scroll = document.body.scrollTop / 1250;
for (var i = 0; i < itemsLength; i++) {
var phase = Math.sin(scroll + (i % 5));
/* When I originally created transX and used it to change the style to transform, it moved
everything right and off the screen partially, I fixed that with CSS */
var transX = items[i].basicLeft + 100 * phase + 'px';
items[i].style.transform = 'translateX('+transX+')';
//items[i].style.left = items[i].basicLeft + 100 * phase + 'px'; < original code I saved for experimental purposes
}
window.animating = false;
// User Timing API to the rescue again. Seriously, it's worth learning.
// Super easy to create custom metrics.
window.performance.mark("mark_end_frame");
window.performance.measure("measure_frame_duration", "mark_start_frame", "mark_end_frame");
if (frame % 10 === 0) {
var timesToUpdatePosition = window.performance.getEntriesByName("measure_frame_duration");
logAverageFrame(timesToUpdatePosition);
}
}
// runs updatePositions on scroll...
//window.addEventListener('scroll', updatePositions); < original code
window.addEventListener('scroll', rAf);
function rAf() {
if(!window.animating) {
window.animating = true;
requestAnimationFrame(updatePositions);
}
}
/* - Generates the sliding pizzas when the page loads. */
document.addEventListener('DOMContentLoaded', function() {
var cols = 8;
var s = 256;
// I moved as much stuff as I could out of the for-loop
var rows = Math.floor(window.screen.height / s); // finds the number of rows by dividing the height of the screen in pixels by the pixels size of each row of moving pizzas
var elementNumber = cols * rows; // decides the number of elements there will be by multiplying the cols by rows
var appendElementsHere = document.querySelector("#movingPizzas1");
for (var i = 0; i < elementNumber; i++) {
var elem = document.createElement('img');
elem.className = 'mover';
elem.src = "images/pizza.png";
/* corrected the proportion of the pizza
and moved it's style to CSS */
elem.basicLeft = (i % cols) * s;
elem.style.top = (Math.floor(i / cols) * s) + 'px';
appendElementsHere.appendChild(elem);
}
// I moved this from updatepositions so that they are pre-loaded global values
items = document.getElementsByClassName('mover'); // & changed from querySelectorAll
itemsLength = items.length;
updatePositions();
});
Changes: style.css
.mover {
position: fixed;
width: 77.333px;
height: 100px;
left: 0;
right: 0;
z-index: -1;
backface-visibility: hidden;
}
Paint flashing of the background pizza's is gone! Thanks andrew_R for this advise on how to use the dev tools, which are very different from in the lessons.
Special thanks to mcs once again for his guidance on the forums here,here, and here. To get translate to work properly I had to fidget with CSS, but it looks good and it seems to be performant to me.
Your challenge, if you wish to accept it (and we sure hope you will), is to optimize this online portfolio for speed! In particular, optimize the critical rendering path and make this page render as quickly as possible by applying the techniques you've picked up in the Critical Rendering Path course.
To get started, check out the repository, inspect the code,
Some useful tips to help you get started:
- Check out the repository
- To inspect the site on your phone, you can run a local server
$> cd /path/to/your-project-folder
$> python -m SimpleHTTPServer 8080
- Open a browser and visit localhost:8080
- Download and install ngrok to make your local server accessible remotely.
$> cd /path/to/your-project-folder
$> ngrok 8080
- Copy the public URL ngrok gives you and try running it through PageSpeed Insights! More on integrating ngrok, Grunt and PageSpeed.
Profile, optimize, measure... and then lather, rinse, and repeat. Good luck!
- Optimizing Performance
- Analyzing the Critical Rendering Path
- Optimizing the Critical Rendering Path
- Avoiding Rendering Blocking CSS
- Optimizing JavaScript
- Measuring with Navigation Timing. We didn't cover the Navigation Timing API in the first two lessons but it's an incredibly useful tool for automated page profiling. I highly recommend reading.
- The fewer the downloads, the better
- Reduce the size of text
- Optimize images
- HTTP caching
The portfolio was built on Twitter's Bootstrap framework. All custom styles are in dist/css/portfolio.css
in the portfolio repo.
Feeling uninspired by the portfolio? Here's a list of cool portfolios I found after a few minutes of Googling.
- A great discussion about portfolios on reddit
- http://ianlunn.co.uk/
- http://www.adhamdannaway.com/portfolio
- http://www.timboelaars.nl/
- http://futoryan.prosite.com/
- http://playonpixels.prosite.com/21591/projects
- http://colintrenter.prosite.com/
- http://calebmorris.prosite.com/
- http://www.cullywright.com/
- http://yourjustlucky.com/
- http://nicoledominguez.com/portfolio/
- http://www.roxannecook.com/
- http://www.84colors.com/portfolio.html