14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/exercises/inefficient-css-1/README.md:
--------------------------------------------------------------------------------
1 | # Learnings
2 |
3 | will-change in combination with transforms or opacity does not trigger paint.
4 |
5 | will-change is a newer property. It allows you to hint to the browser that an element is going to change in the future. This allows the browser to optimize for this change ahead of time.
6 |
7 | Use it sparingly and only when you know that an element is going to change. Otherwise, you may end up with worse performance. Every performance optimization comes with a cost, but not always a benefit.
8 |
9 | Used Paint Flashing in Rendering of Chrome DevTools to see what is being painted.
10 |
--------------------------------------------------------------------------------
/exercises/inefficient-css-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Inefficient CSS Debugging Exercise
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/exercises/excessive-dom-size copy/README.md:
--------------------------------------------------------------------------------
1 | # Learnings
2 |
3 | - processing in batch and single number when doing generator functions is the same speed
4 | - The reason the Web Worker seems fast even though the message to start processing is sent on a button click is that the heavy computation doesn't block the UI thread. As soon as the user clicks the button, the Web Worker starts the task in the background, allowing the main thread to continue running smoothly.
5 | - The Web Worker does not start any computation until it receives the message to start. The speed comes from the fact that it runs in parallel and doesn't interrupt or slow down the main thread, not from pre-computing the result.
6 |
--------------------------------------------------------------------------------
/exercises/poor-event-listener-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Poorly Managed Event Listeners Exercise
7 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/exercises/inefficient-css-2/script.js:
--------------------------------------------------------------------------------
1 | // document.getElementById("updateButton").addEventListener("click", () => {
2 | // const dynamicContent = document.querySelector(".dynamic-content");
3 | // dynamicContent.innerHTML = "";
4 |
5 | // for (let i = 0; i < 50; i++) {
6 | // const newElement = document.createElement("p");
7 | // newElement.textContent = `Item ${i + 1}`;
8 | // dynamicContent.appendChild(newElement);
9 | // }
10 | // });
11 |
12 | document.getElementById("updateButton").addEventListener("click", () => {
13 | const dynamicContent = document.querySelector(".dynamic-content");
14 |
15 | // Instead of clearing and repopulating everything, just add new items
16 | for (let i = dynamicContent.children.length; i < 50; i++) {
17 | const newElement = document.createElement("p");
18 | newElement.textContent = `Item ${i + 1}`;
19 | dynamicContent.appendChild(newElement);
20 | }
21 | });
22 |
--------------------------------------------------------------------------------
/exercises/detached-dom-objects/script.js:
--------------------------------------------------------------------------------
1 | let items = [];
2 |
3 | const addItem = () => {
4 | const container = document.getElementById("container");
5 | const newItem = document.createElement("div");
6 | newItem.classList.add("item");
7 | newItem.textContent = "New Item";
8 | container.appendChild(newItem);
9 |
10 | items.push(newItem); // Store reference to each item
11 | };
12 |
13 | const removeItems = () => {
14 | const container = document.getElementById("container");
15 | container.innerHTML = ""; // Clear container
16 | };
17 |
18 | document.getElementById("addItems").addEventListener("click", addItem);
19 | document.getElementById("removeItems").addEventListener("click", removeItems);
20 |
21 | // let items = [];
22 |
23 | // const addItem = () => {
24 | // const container = document.getElementById("container");
25 | // const newItem = document.createElement("div");
26 | // newItem.classList.add("item");
27 | // newItem.textContent = "New Item";
28 | // container.appendChild(newItem);
29 |
30 | // items.push(newItem); // Store reference to each item
31 | // };
32 |
33 | // const removeItems = () => {
34 | // const container = document.getElementById("container");
35 | // container.innerHTML = ""; // Clear container
36 |
37 | // items = []; // Clear the array to remove references to DOM elements
38 | // };
39 |
40 | // document.getElementById("addItems").addEventListener("click", addItem);
41 | // document.getElementById("removeItems").addEventListener("click", removeItems);
42 |
--------------------------------------------------------------------------------
/exercises/layout-thrashing/script.js:
--------------------------------------------------------------------------------
1 | // document.getElementById("addBoxes").addEventListener("click", () => {
2 | // const container = document.getElementById("container");
3 |
4 | // // Simulate layout thrashing
5 | // for (let i = 0; i < 50; i++) {
6 | // const box = document.createElement("div");
7 | // box.classList.add("box");
8 | // container.appendChild(box);
9 |
10 | // // Triggering layout calculations inside a loop
11 | // console.log(box.offsetWidth, box.offsetHeight);
12 | // }
13 | // });
14 |
15 | // document.getElementById("addBoxes").addEventListener("click", () => {
16 | // const container = document.getElementById("container");
17 | // const boxes = [];
18 |
19 | // // First, perform all DOM writes
20 | // for (let i = 0; i < 50; i++) {
21 | // const box = document.createElement("div");
22 | // box.classList.add("box");
23 | // container.appendChild(box);
24 | // boxes.push(box);
25 | // }
26 |
27 | // // Then, perform all DOM reads
28 | // boxes.forEach((box) => {
29 | // console.log(box.offsetWidth, box.offsetHeight);
30 | // });
31 | // });
32 |
33 | document.getElementById("addBoxes").addEventListener("click", () => {
34 | const container = document.getElementById("container");
35 |
36 | const addBox = (i) => {
37 | if (i < 50) {
38 | const box = document.createElement("div");
39 | box.classList.add("box");
40 | container.appendChild(box);
41 |
42 | // Defer layout calculations to the next repaint and not in the same frame as the DOM write
43 | requestAnimationFrame(() => {
44 | console.log(box.offsetWidth, box.offsetHeight);
45 | addBox(i + 1); // Add the next box
46 | });
47 | }
48 | };
49 |
50 | addBox(0); // Start adding boxes
51 | });
52 |
--------------------------------------------------------------------------------
/exercises/excessive-dom-size copy/script.js:
--------------------------------------------------------------------------------
1 | // document.getElementById("processData").addEventListener("click", () => {
2 | // const start = performance.now();
3 | // const resultDiv = document.getElementById("result");
4 | // let result = 0;
5 |
6 | // for (let i = 0; i < 100000000; i++) {
7 | // result += Math.sqrt(i);
8 | // }
9 |
10 | // const duration = performance.now() - start;
11 | // resultDiv.textContent = `Result: ${result} (Time: ${duration.toFixed(2)}ms)`; // Result: 666666661666.567 (Time: 117.20ms)
12 | // });
13 |
14 | // function* processNumbers() {
15 | // let result = 0;
16 | // for (let i = 0; i < 100000000; i++) {
17 | // result += Math.sqrt(i);
18 | // if (i % 1000000 === 0) yield;
19 | // }
20 | // return result;
21 | // }
22 |
23 | // document.getElementById("processData").addEventListener("click", () => {
24 | // const start = performance.now();
25 | // const resultDiv = document.getElementById("result");
26 | // const generator = processNumbers();
27 |
28 | // function processChunk() {
29 | // const next = generator.next();
30 | // if (!next.done) {
31 | // requestAnimationFrame(processChunk);
32 | // } else {
33 | // const duration = performance.now() - start;
34 | // resultDiv.textContent = `Result with Generator: ${
35 | // next.value
36 | // } (Time: ${duration.toFixed(2)}ms)`; // Result with Generator: 666666661666.567 (Time: 1317.80ms)
37 | // }
38 | // }
39 |
40 | // processChunk();
41 | // });
42 |
43 | // function* processNumbersBatch() {
44 | // let result = 0;
45 | // let batchSize = 1000000;
46 |
47 | // for (let i = 0; i < 100000000; i += batchSize) {
48 | // for (let j = i; j < i + batchSize && j < 100000000; j++) {
49 | // result += Math.sqrt(j);
50 | // }
51 | // yield;
52 | // }
53 | // return result;
54 | // }
55 |
56 | // document.getElementById("processData").addEventListener("click", () => {
57 | // const start = performance.now();
58 | // const resultDiv = document.getElementById("result");
59 | // const generator = processNumbersBatch();
60 |
61 | // function processChunk() {
62 | // const next = generator.next();
63 | // if (!next.done) {
64 | // requestAnimationFrame(processChunk);
65 | // } else {
66 | // const duration = performance.now() - start;
67 | // resultDiv.textContent = `Result with Batched Generator: ${
68 | // next.value
69 | // } (Time: ${duration.toFixed(2)}ms)`; // Result with Batched Generator: 666666661666.567 (Time: 1310.70ms)
70 | // }
71 | // }
72 |
73 | // processChunk();
74 | // });
75 |
76 | // if (window.Worker) {
77 | // const myWorker = new Worker("worker.js");
78 |
79 | // document.getElementById("processData").addEventListener("click", () => {
80 | // const start = performance.now();
81 | // myWorker.postMessage("start");
82 |
83 | // myWorker.onmessage = function (e) {
84 | // const duration = performance.now() - start;
85 | // document.getElementById(
86 | // "result"
87 | // ).textContent = `Result with Web Worker: ${
88 | // e.data
89 | // } (Time: ${duration.toFixed(2)}ms)`; // Result with Web Worker: 666666661666.567 (Time: 120.60ms)
90 | // };
91 | // });
92 | // } else {
93 | // console.log("Your browser doesn't support web workers.");
94 | // }
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web Performance Notes
2 |
3 | # Common issues
4 |
5 | 1. **Unoptimized Images**: Large or unoptimized images can significantly slow down page load times. This is one of the most common issues, as images are a major part of most websites.
6 |
7 | 2. **Excessive JavaScript Execution**: Heavy or inefficient JavaScript can block the main thread, leading to delays in page interactivity and rendering.
8 |
9 | 3. **Too Many Render-Blocking Resources**: CSS and JavaScript files that are loaded in the head of your document without async or defer attributes can block the page from rendering quickly.
10 |
11 | 4. **Unnecessary Downloads**: Loading resources that aren't immediately needed, such as off-screen images or features only used by a small percentage of users.
12 |
13 | 5. **Lack of Compression**: Not using text compression (like Gzip or Brotli) for your resources can increase the amount of data transferred, affecting load time.
14 |
15 | 6. **Inefficient CSS Selectors**: Complex or inefficient CSS selectors can slow down the rendering process, as the browser has to spend more time calculating styles.
16 |
17 | 7. **Excessive DOM Size**: A large DOM can lead to increased memory usage and slower performance, especially during DOM manipulation operations.
18 |
19 | 8. **Memory Leaks**: JavaScript memory leaks can lead to increased memory usage over time, eventually causing the page to slow down or crash.
20 |
21 | 9. **Poor Cache Strategy**: Not leveraging browser caching effectively can lead to unnecessary network requests, slowing down repeat visits to your page.
22 |
23 | 10. **Slow Third-Party Scripts**: Dependency on third-party scripts and services (like ads, analytics, or widgets) can significantly impact performance if these scripts are slow or unresponsive.
24 |
25 | # Analyzing the performance data
26 |
27 | ## Interpreting the flame chart
28 |
29 | ### Understanding the Flame Chart
30 |
31 | - **Time Axis**: The horizontal axis represents time.
32 | - **Call Stack**: Each row in the flame chart represents a function call stack at any point in time.
33 | - **Bars**: Each bar represents a function call. The width of the bar indicates the time taken by that function.
34 |
35 | ### Interpreting the Data
36 |
37 | - **Wide Bars**: Indicate functions that took a long time to execute. These are your primary targets for optimization.
38 | - **Tall Stacks**: A tall stack of bars represents a deep call stack, suggesting complex function executions.
39 | - **Color Coding**: Different colors can represent different types of activities (e.g., scripting, rendering).
40 | - **Hover for Details**: Hover over bars to see more information about the function, including execution time and resources used.
41 |
42 | ### Analyze deep call stacks
43 |
44 | 1. **Identify the Root Cause**: Look at the bottom of the stack to understand what initiated the series of calls.
45 | 2. **Trace the Execution Path**: Follow the stack upwards to see the sequence of function calls and how the execution flows through different layers of your application.
46 | 3. **Look for Anomalies**: Unusually deep or unexpected paths might indicate inefficiencies or bugs, like unintended recursive calls or unnecessary nested functions.
47 |
48 | ## Call Tree and Bottom-Up Views
49 |
50 | **Examine the Call Tree:**
51 |
52 | Switch to the "Bottom-Up" or "Call Tree" tab to see where most time is spent.
53 |
54 | Focus on high self-time functions.
55 |
56 | ## Tips for effective analysis
57 |
58 | - **Look for Wide Bars**: In the flame chart, wide bars indicate functions that took a long time to execute. These are your primary targets for optimization.
59 | - **Identify Long Tasks**: Any task that takes over 50ms can impact user responsiveness. In the overview graph, these are often marked with a red triangle.
60 | - **Check Call Stacks**: Deep stacks can indicate complex calculations or recursive functions.
61 | - **Correlate with Events**: Use the overview graph to correlate scripting or rendering spikes with specific events like user interactions or network responses.
62 |
63 | # Memory profiling
64 |
65 | ## What is the heap?
66 |
67 | The heap is a region of computer memory that's used to store dynamic data structures. It's where objects, strings, and closures are allocated when your code is running.
68 |
69 | ## Take heap snapshots
70 |
71 | Go to memory panel and take a heap snapshot.
72 |
73 | ## Interpreting heap snapshots
74 |
75 | **Look at Constructor Names:** This helps you identify objects by their types.
76 |
77 | Sort by Shallow Size or Retained Size.
78 |
79 | **Shallow Size:** The memory taken up by the object itself.
80 | **Retained Size:** The total size of memory that can be freed up if this object is removed, including its dependencies.
81 |
82 | **Check for Unexpected Large Objects:** Objects consuming a large amount of memory could be a sign of a memory leak.
83 |
84 | Use the **Containment** and **Dominators** Views: These views help to understand the hierarchy and the ownership of objects.
85 |
86 | ## Identify objects for garbage collection
87 |
88 | An object should be garbage collected when it is no longer reachable or needed in your application. This typically occurs when:
89 |
90 | - There are no references to it from other live objects or global variables.
91 | - It’s not part of a closure, event listener, or callback that’s still in use.
92 |
93 | In heap snapshots, investigate objects with no clear references or those that are unexpectedly large and don't seem to be in use anymore.
94 |
95 | ## Check for detached DOM trees
96 |
97 | Detached DOM trees are collections of DOM elements that are no longer part of the live DOM but are still held in memory by JavaScript references.
98 |
99 | **How to Identify:**
100 |
101 | - In the heap snapshot, look for DOM nodes that have no parent or whose parent is not part of the current document.
102 | - Use the 'DOM Breakdown' tab to see a summary of detached DOM elements.
103 |
104 | # Critical Rendering Path
105 |
106 | The Critical Rendering Path (CRP) is the process by which a browser converts HTML, CSS, and JavaScript into pixels on the screen. It's a sequence of steps that the browser goes through to render a web page.
107 |
108 | **HTML Parsing and DOM Construction:** The browser reads the HTML and creates the Document Object Model (DOM), a tree structure representing all HTML elements.
109 |
110 | **CSS Parsing and CSSOM Construction:** The browser parses CSS files and inline styles, creating the CSS Object Model (CSSOM), which represents the styling information for each DOM node.
111 |
112 | **Render Tree Construction:** The browser combines the DOM and CSSOM to create a render tree, which contains only the nodes required to render the page, along with their styles.
113 |
114 | **Layout:** The browser calculates the size and position of each node in the render tree. This process is also known as "reflow."
115 |
116 | **Paint:** The browser fills in pixels for each node, applying text, colors, images, etc.
117 |
118 | **Composite:** Layers are drawn onto the screen in the correct order to create the final visual output.
119 |
120 | ```
121 | [HTML] --> [DOM]
122 | [CSS] --> [CSSOM]
123 | [DOM + CSSOM] --> [Render Tree] --> [Layout] --> [Paint] --> [Composite]
124 | ```
125 |
126 | # Layout Reflow
127 |
128 | A layout reflow is the process by which the browser recalculates the positions and dimensions of elements in the document. It's a critical step in the rendering process, where the browser determines where to place each element on the page.
129 |
130 | ## How they occur
131 |
132 | **DOM Manipulation:** Any script that changes the layout of a part of the page (like altering the height of an element) will trigger a reflow of that element, its children, and possibly siblings and ancestors.
133 |
134 | **Style Changes:** Changing styles that affect the size or position of elements (like width, height, margin, padding, border, etc.) will also trigger a reflow.
135 |
136 | **Computed Style Access:** Accessing certain properties in JavaScript, like offsetWidth, scrollTop, or getComputedStyle(), can also trigger reflow because the browser needs to ensure it returns the correct value.
137 |
138 | # Layout Thrashing
139 |
140 | When your JavaScript repeatedly reads and then writes to the DOM, the browser must reflow the layout after each read/write cycle. If these cycles are numerous and close together, it causes layout thrashing.
141 |
142 | Each reflow operation is computationally expensive. In a thrashing scenario, the browser ends up performing far more layout calculations than necessary, consuming significant CPU time and slowing down the page.
143 |
144 | ## How to avoid
145 |
146 | **Batch DOM Read/Write Operations:**
147 |
148 | Group all your DOM reads together first, and then perform all the writes. This minimizes the number of reflow cycles.
149 |
150 | Example: If you need to read then write to multiple elements, read from all of them first, then write to all of them.
151 |
152 | **Use Document Fragments:**
153 |
154 | Build your changes off-DOM using document fragments, and then append the fragment to the DOM as a single operation.
155 |
156 | **Optimize CSS:**
157 |
158 | Simplify CSS selectors to reduce the time the browser spends recalculating styles.
159 |
160 | **Use requestAnimationFrame:**
161 |
162 | This method allows you to queue changes to be made in the next repaint, reducing the number of reflows.
163 |
164 | **Debounce and Throttle:**
165 |
166 | For operations tied to events like resize or scroll, use debounce or throttle techniques to limit the number of times these operations are executed.
167 |
--------------------------------------------------------------------------------