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 and 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 the top-level of your project directory to make your local server accessible remotely.
$> cd /path/to/your-project-folder
$> ./ngrok http 8080
- Copy the public URL ngrok gives you and try running it through PageSpeed Insights! Optional: More on integrating ngrok, Grunt and PageSpeed.
Profile, optimize, measure... and then lather, rinse, and repeat. Good luck!
-
Put all blocking CSS and scripts at the bottom of the index.html.
<link href="css/style.css" rel="stylesheet">
-
Concatenated the print.css file with the styles.css file:
<!-- <link href="css/print.css" rel="stylesheet"> concatenated in style.css-->
-
Put
async
on main.js to avoid render blocking js:<script async src="js/perfmatters.js"></script>
-
Removed the webfonts link completely. Fonts are still acceptable. TODO: Find another way to import fonts without lowering pageSpeed scores.
-
Optimized personal profile picture and Cam's pizzeria picture using tinyjpg.com and by rescaling with Gimp. The size of the
pizzeria.img
went from over 2 MB to 7 KB. It is renamedpizzeria-min-scaled.jpg
. -
Latest run on PageSpeed Insights gave a mobile score of 99 (and a desktop of 93).
To optimize views/pizza.html, you will need to modify views/js/main.js until your frames per second rate is 60 fps or higher. You will find instructive comments in main.js.
You might find the FPS Counter/HUD Display useful in Chrome developer tools described here: Chrome Dev Tools tips-and-tricks.
On scrolling the most notable performance bottleneck was the updatePositions()
function. The basic plan of attack was to remove unecessary cycles and decrease file sizes.
-
took
scrollTop = .... / 1250
out of the for loop as it is a constant at call time and doesn't need to be called repeatedly. -
changed
var items = document.querySelectorAll('.mover');
to an array called movers and used the more efficientgetElementsByClassName
method:var movers = document.getElementsByClassName('mover');
-
the sliding pizzas created on loading the page were originally 200. I noticed at maximum screen width there would be a possible total of 3 rows by 8 columns. In the for loop of the
addEventListener
function I changedi
from 200 to 24.for (var i = 0; i < 24; i++)
TODO: Figure a way to automatically adjust the number of pizzas provided for different screen sizes. -
the
pizza.png
file which represents the moving pizzas was originally 49 KB. I minimized this to 19 KB, again using tinyjpg.com -
the main
pizzeria.jpg
file at the top of the page was also set upon for a size loss. The same method was used as above and the size went down from 2079 KB to 270 KB. It was renamedpizzeria-min.jpg
. Needless to say, this considerably reduced first page load time. -
put
getAnimationFrame
aroundupdatePositions()
on loading the page. This showed a slight improvement as it removed thelayout
portion on the timeline. -
used
will-change: transform
on the.mover
in the css file. This changed the mover pizzas to another layer when scrolling. -
in
updatePositons
used transforms to remove painting from the critical path:movers[i].style.transform = "translateX(" + (phaseArray[i % 5]) + "px)";
Also changed the.basicWidth
property to.left
because the transform method would relate to the orignal.left
property for calculations. -
set the number of moving pizza elements to be created on loading to fit the screen size.
var cols = 8;//as suggested in review to avoid # of phases = cols
var rows = screen.height / 225;
var PizzaOrderSize = Math.floor(rows * cols);
TODO: this could be modified to recalcuate on window resizing or for mobile
-
created an independent array
phaseArray
outside of the main for loop:var phaseArray = [];
for (var i = 0; i < 5; i++) {
phaseArray[i] = Math.sin((scrollTop) + (i)) * 100;} //removed modulus as i = i%5
This was supposed to make less calculations, only 5, and enhance performance. I did not notice a great improvement. Calling the array from inside the next
for
loop for the position transform may be offsetting these gains. -
as suggested in the first review I removed the selection of pizzasDiv from the for loop as only needed once:
var pizzasDiv = document.getElementById("randomPizzas");
for (var i = 2; i < menuLength; i++) {
pizzasDiv.appendChild(pizzaElementGenerator(i));}
-
lastly I put the files through an online minifier as I was still struggling with my gulpfile when minimizing css with the
uglify()
module.
the first performance recording indicated that the determineDx
function was a major bottleneck and causing 'Forced Reflow'errors. Inspecting the code it was seen that changePizzaSizes()
was calling determineDx
in a loop and also recalculating the newwidth
variable everytime. In addition it was using a querySelectorAll
on multiple lines just to assign the newwidth
to the array elements' style.width
-
in
changePizzaSizes()
I removed the need to use multiple calls to thequerySelectorAll
method by declaring an array calledallPizzas
that found its elements by thegetElementsByClassName
method. -
looking at the repeated calls to
determineDx
it was noted thatdx
could be calculated using constants. BothwindowWidth
andoffsetWidth
moved to the top of theresizePizzas
function to be called once per cycle. This reduced the time to resize pizzas from 270 ms approx. to 28 ms approx. No more forced flows were noted in the performance recordings. -
it was noted that
offsetWidth
was at call time the same asoldWidth
. This eliminated the need forvar oldWidth = elem.offsetWidth
declaration and a new line of code was written outside of thedetermineDx
function:var oldSize = offsetWidth / windowWidth
. -
at this point
determineDx
could be reduced to one simple calculation. ThesizeSwitcher()
function was left independent to pass thenewSize
anddetermineDx
removed:var newSize = sizeSwitcher(size); var dx = (newSize - oldSize) * windowWidth;
-
a
newWidth
parameter was calculated using:var newwidth = offsetWidth + dx) + 'px';
and passed on to thechangePizzaSizes()
to resize theallPizzas
array elements' width style. This reduced time to resize pizzas to approx. 1.5 ms.
TODO: I started to write a gulpfile.js
to improve speeds by minimizing css and js. I managed to get most of the work done and sent to a dist
folder. However, I got errors when trying to minimize css and will have to work on this later. I did learn a lot and went through treehouse's GULP COURSE.
- 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.