1
+ // PerfMap - Front-end performance heatmap at https://github.com/zeman/perfmap
2
+ var gZeroLeft = 0 ;
3
+ var gZeroTop = 0 ;
4
+ var gWinWidth = window . innerWidth || document . documentElement . clientWidth ;
5
+
6
+ function findImages ( ) {
7
+ var aElems = document . getElementsByTagName ( '*' ) ;
8
+ var re = / u r l \( ( " ? h t t p .* " ? ) \) / ig;
9
+ for ( var i = 0 , len = aElems . length ; i < len ; i ++ ) {
10
+ var elem = aElems [ i ] ;
11
+ var style = window . getComputedStyle ( elem ) ;
12
+ var url = elem . src || elem . href ;
13
+ var hasImage = 0 ;
14
+ var fixed = 0 ;
15
+ var body = 0 ;
16
+ re . lastIndex = 0 ; // reset state of regex so we catch repeating spritesheet elements
17
+ if ( elem . tagName == 'IMG' ) {
18
+ hasImage = 1 ;
19
+ }
20
+ if ( style [ 'backgroundImage' ] ) {
21
+ var backgroundImage = style [ 'backgroundImage' ] ;
22
+ var matches = re . exec ( style [ 'backgroundImage' ] ) ;
23
+ if ( matches && matches . length > 1 ) {
24
+ url = backgroundImage . substring ( 4 ) ;
25
+ url = url . substring ( 0 , url . length - 1 ) ;
26
+ url = url . replace ( / " / , "" ) ;
27
+ url = url . replace ( / " / , "" ) ;
28
+ hasImage = 1 ;
29
+ if ( elem . tagName == 'BODY' ) {
30
+ body = 1 ;
31
+ }
32
+ }
33
+ }
34
+ if ( style [ 'visibility' ] == "hidden" ) {
35
+ hasImage = 0 ;
36
+ }
37
+ if ( hasImage == 1 ) {
38
+ if ( url ) {
39
+ var entry = performance . getEntriesByName ( url ) [ 0 ] ;
40
+ if ( entry ) {
41
+ var xy = getCumulativeOffset ( elem , url ) ;
42
+ var wh = elem . getBoundingClientRect ( ) ;
43
+ var width = wh . width ;
44
+ var height = wh . height ;
45
+ if ( width > 10 ) {
46
+ if ( height > 10 ) {
47
+ placeMarker ( xy , width , height , entry , body , url ) ;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ function placeMarker ( xy , width , height , entry , body , url ) {
57
+ var heat = entry . responseEnd / loaded ;
58
+ // adjust size of fonts/padding based on width of overlay
59
+ if ( width < 170 ) {
60
+ var padding = 12 ;
61
+ var size = 12 ;
62
+ } else if ( width > 400 ) {
63
+ var padding = 13 ;
64
+ var size = 26 ;
65
+ } else {
66
+ var padding = 9 ;
67
+ var size = 18 ;
68
+ }
69
+ // check for overlay that matches viewport and assume it's like a background image on body
70
+ if ( width == document . documentElement . clientWidth ) {
71
+ if ( height >= document . documentElement . clientHeight ) {
72
+ body = 1 ;
73
+ }
74
+ }
75
+ // adjust opacity if it's the body element and position label top right
76
+ if ( body == 1 ) {
77
+ var opacity = 0.6 ;
78
+ var size = 18 ;
79
+ var align = "right" ;
80
+ var paddingTop = 10 ;
81
+ var bodyText = "BODY " ;
82
+ } else {
83
+ var opacity = 0.925 ;
84
+ var align = "center" ;
85
+ var paddingTop = ( height / 2 ) - padding ;
86
+ var bodyText = "" ;
87
+ }
88
+ var marker = document . createElement ( "div" ) ;
89
+ marker . className = "perfmap" ;
90
+ marker . setAttribute ( "data-ms" , parseInt ( entry . responseEnd ) ) ;
91
+ marker . setAttribute ( "data-body" , body ) ;
92
+ marker . setAttribute ( "dir" , "ltr" ) ; // Force LTR display even if injected on an RTL page
93
+ marker . style . cssText = "position:absolute; transition: 0.5s ease-in-out; box-sizing: border-box; color: #fff; padding-left:10px; padding-right:10px; line-height:14px; font-size: " + size + "px; font-weight:800; font-family:\"Helvetica Neue\",sans-serif; text-align:" + align + "; opacity: " + opacity + "; " + heatmap ( heat ) + " top: " + xy . top + "px; left: " + xy . left + "px; width: " + width + "px; height:" + height + "px; padding-top:" + paddingTop + "px; z-index: 4000;" ;
94
+ if ( width > 50 ) {
95
+ if ( height > 15 ) {
96
+ marker . innerHTML = bodyText + parseInt ( entry . responseEnd ) + "ms (" + parseInt ( entry . duration ) + "ms)" ;
97
+ }
98
+ }
99
+ document . body . appendChild ( marker ) ;
100
+ }
101
+
102
+ function heatmap ( heat ) {
103
+ if ( heat < 0.16 ) {
104
+ return "background: #1a9850;"
105
+ }
106
+ else if ( heat < 0.32 ) {
107
+ return "background: #66bd63;"
108
+ }
109
+ else if ( heat < 0.48 ) {
110
+ return "background: #a6d96a;"
111
+ }
112
+ else if ( heat < 0.64 ) {
113
+ return "background: #fdae61;"
114
+ }
115
+ else if ( heat < 0.8 ) {
116
+ return "background: #f46d43;"
117
+ } else {
118
+ return "background: #d73027;"
119
+ }
120
+ }
121
+
122
+ function getCumulativeOffset ( obj , url ) {
123
+ var left , top ;
124
+ left = top = 0 ;
125
+ if ( obj . offsetParent ) {
126
+ do {
127
+ left += obj . offsetLeft ;
128
+ top += obj . offsetTop ;
129
+ } while ( obj = obj . offsetParent ) ;
130
+ }
131
+ if ( 0 == top ) {
132
+ left += gZeroLeft ;
133
+ top += gZeroTop ;
134
+ }
135
+ return {
136
+ left :left ,
137
+ top :top ,
138
+ } ;
139
+ }
140
+
141
+ // give visual feedback asap
142
+ var loading = document . createElement ( "div" ) ;
143
+ loading . id = "perfmap-loading" ;
144
+ loading . innerHTML = "Creating PerfMap" ;
145
+ loading . style . cssText = "position:absolute; z-index:6000; left:40%; top:45%; background-color:#000; color:#fff; padding:20px 30px; font-family:\"Helvetica Neue\",sans-serif; font-size:24px; font-weight:800;border:2px solid white;" ;
146
+ document . body . appendChild ( loading ) ;
147
+
148
+ // get full page load time to calculate heatmap max
149
+ var loaded = performance . timing . loadEventEnd - performance . timing . navigationStart ;
150
+
151
+ // backend
152
+ var backend = performance . timing . responseEnd - performance . timing . navigationStart ;
153
+ var backendLeft = ( backend / loaded ) * 100 ;
154
+
155
+ // first paint in chrome from https://github.com/addyosmani/timing.js
156
+ var hasFirstPaint = 0 ;
157
+ if ( window . chrome && window . chrome . loadTimes ) {
158
+ var paint = window . chrome . loadTimes ( ) . firstPaintTime * 1000 ;
159
+ var firstPaint = paint - ( window . chrome . loadTimes ( ) . startLoadTime * 1000 ) ;
160
+ var firstPaintLeft = ( firstPaint / loaded ) * 100 ;
161
+ hasFirstPaint = 1 ;
162
+ }
163
+
164
+ // remove any exisiting "perfmap" divs on second click
165
+ var elements = document . getElementsByClassName ( "perfmap" ) ;
166
+ while ( elements . length > 0 ) {
167
+ elements [ 0 ] . parentNode . removeChild ( elements [ 0 ] ) ;
168
+ }
169
+
170
+ // build bottom legend
171
+ var perfmap = document . createElement ( "div" ) ;
172
+ perfmap . id = "perfmap" ;
173
+ var legend = "<div style='width:16.666666667%; height: 50px; float:left; background-color:#1a9850;'></div><div style='width:16.666666667%; height: 50px; float:left; background-color:#66bd63;'></div><div style='width:16.666666667%; height: 50px; float:left; background-color:#a6d96a;'></div><div style='width:16.666666667%; height: 50px; float:left; background-color:#fdae61;'></div><div style='width:16.666666667%; height: 50px; float:left; background-color:#f46d43;'></div><div style='width:16.666666667%; height: 50px; float:left; background-color:#d73027;'></div><div style='position:absolute; z-index:2; right:0px; padding-top:5px; padding-right:10px;height:100%;color:#fff;'>Fully Loaded " + parseInt ( loaded ) + "ms</div><div id='perfmap-timeline' style='position:absolute; z-index:4; left:-100px; border-left:2px solid white;height:100%;'></div>" ;
174
+ if ( hasFirstPaint == 1 ) {
175
+ legend += "<div style='position:absolute; z-index:3; left:" + firstPaintLeft + "%; padding-top:5px; border-left:2px solid white;padding-left:5px;height:100%;color:#fff;'>First Paint " + parseInt ( firstPaint ) + "ms</div></div>" ;
176
+ }
177
+ perfmap . style . cssText = "position: fixed; width:100%; bottom:0; left:0; z-index:5000; height: 25px; color:#fff; font-family:\"Helvetica Neue\",sans-serif; font-size:14px; font-weight:800; line-height:14px;" ;
178
+ perfmap . innerHTML = legend ;
179
+ document . body . appendChild ( perfmap ) ;
180
+
181
+ // build heatmap
182
+ findImages ( ) ;
183
+
184
+ // remove loading message
185
+ loading . remove ( ) ;
186
+
187
+ // mouse events to move timeline around on hover
188
+ var elements = document . getElementsByClassName ( "perfmap" ) ;
189
+ var timeline = document . getElementById ( 'perfmap-timeline' ) ;
190
+ for ( var i = 0 , len = elements . length ; i < len ; i ++ ) {
191
+ elements [ i ] . onmouseover = function ( ) {
192
+ var timelineLeft = document . documentElement . clientWidth * ( this . dataset . ms / loaded ) ;
193
+ if ( this . dataset . body != "1" ) {
194
+ this . style . opacity = 1 ;
195
+ }
196
+ timeline . style . cssText = "opacity:1; transition: 0.5s ease-in-out; transform: translate(" + parseInt ( timelineLeft ) + "px,0); position:absolute; z-index:4; border-left:2px solid white; height:100%;" ;
197
+ }
198
+ elements [ i ] . onmouseout = function ( ) {
199
+ var timelineLeft = document . documentElement . clientWidth * ( this . dataset . ms / loaded ) ;
200
+ if ( this . dataset . body != "1" ) {
201
+ this . style . opacity = 0.925 ;
202
+ }
203
+ timeline . style . cssText = "opacity:0; transition: 0.5s ease-in-out; transform: translate(" + parseInt ( timelineLeft ) + "px,0); position:absolute; z-index:4; border-left:2px solid white; height:100%;" ;
204
+ }
205
+ }