diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fb88a33
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+.grunt
+
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..0c3989c
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,21 @@
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 4,
+ "latedef": false,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "smarttabs": true,
+ "strict": true,
+ "trailing": true,
+ "undef": true,
+ "validthis": true
+}
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..75a375c
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,56 @@
+module.exports = function(grunt) {
+
+ // Project configuration.
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ wrap: {
+ basic: {
+ src: ['build/perfmap.js'],
+ dest: 'build/perfmap.js',
+ options: {
+ wrapper: ['\n;(function(window, document, undefined){\n\'use strict\';\n', '\nwindow.perfMap = perfMap;\n})(window, document);\nperfMap.init();']
+ }
+ }
+ },
+ concat: {
+ options: {
+ stripBanners: true
+ },
+ dist: {
+ src: 'src/**/*.js',
+ dest: 'build/perfmap.js'
+ }
+ },
+ jshint: {
+ //src: ['src/**/*.js'],
+ src: ['build/perfmap.js'],
+ options: {
+ jshintrc: '.jshintrc'
+ }
+ },
+ jasmine: {
+ // Your project's source files
+ src: 'src/**/*.js',
+ options: {
+ specs: 'specs/**/*spec.js'
+ }
+ },
+ lenient: {
+ src: 'build/perfmap.js',
+ dest: 'build/perfmap.js'
+ }
+ });
+
+ // Register tasks.
+ grunt.loadNpmTasks('grunt-contrib-jshint');
+ grunt.loadNpmTasks('grunt-wrap');
+ grunt.loadNpmTasks('grunt-lenient');
+ grunt.loadNpmTasks('grunt-contrib-concat');
+ grunt.loadNpmTasks('grunt-contrib-jasmine');
+
+ // Default task.
+ grunt.registerTask('default', ['jasmine', 'concat:dist', 'jshint']);
+ grunt.registerTask('test', ['jasmine']);
+ grunt.registerTask('build', ['concat:dist', 'lenient', 'wrap:basic', 'jasmine', 'jshint']);
+
+};
\ No newline at end of file
diff --git a/build/perfmap.js b/build/perfmap.js
new file mode 100644
index 0000000..dd3af4c
--- /dev/null
+++ b/build/perfmap.js
@@ -0,0 +1,307 @@
+
+;(function(window, document, undefined){
+'use strict';
+
+var perfMap = {},
+ loading,
+ loaded,
+ gZeroLeft = 0,
+ gZeroTop = 0,
+ hArr = [{
+ threashold: 0.16,
+ value: '#1a9850',
+ rgba: 'rgba(26, 152, 80, 0.95)'
+ }, {
+ threashold: 0.32,
+ value: '#66bd63',
+ rgba: 'rgba(102, 189, 99, 0.95)'
+ }, {
+ threashold: 0.48,
+ value: '#a6d964',
+ rgba: 'rgba(166, 217, 100, 0.95)'
+ }, {
+ threashold: 0.64,
+ value: '#fdae61',
+ rgba: 'rgba(253, 174, 97, 0.95)'
+ }, {
+ threashold: 0.8,
+ value: '#f46d43',
+ rgba: 'rgba(244, 109, 67, 0.95)'
+ }, {
+ threashold: 1.1,
+ value: '#d73027',
+ rgba: 'rgba(215, 48, 39, 0.95)'
+ }];
+
+
+function findImages() {
+ var tags = document.getElementsByTagName('*'),
+ images = document.getElementsByTagName('img'),
+ el,
+ len,
+ imgs = [];
+ //re = /url\(([http].*)\)/ig;
+ //re = /(url)\((.*?)\)/ig;
+
+ imgs = getTagImages(document);
+ len = tags.length;
+ for (var j = 0; j < len; j++) {
+ el = getBgElement(tags[j]);
+ if (!!el && !!el.src) {
+ el.bgImg = el.src;
+
+ var match = el.src.match(/\((.*?)\)/);
+ if (match[1]) {
+ el.src = match[1].replace(/('|")/g, '');
+ /*if (style['visibility'] == "hidden") {
+ hasImage = 0;
+ } else {
+ hasImage = 1;
+ if (elem.tagName == 'BODY') {
+ body = 1;
+ }
+ imgs.push(el);
+ }*/
+ imgs.push(el);
+ }
+ }
+ }
+
+
+ //console.log(imgs);
+ len = imgs.length;
+ for (var i = 0; i < len; i++) {
+ var entry = window.performance.getEntriesByName(imgs[i].src)[0];
+ if (entry) {
+ //var xy = getCumulativeOffset(imgs[i].element, imgs[i].src);
+ var wh = imgs[i].element.getBoundingClientRect();
+ var width = wh.width;
+ var height = wh.height;
+ if (width > 10) {
+ if (height > 10) {
+ placeMarker(width, height, entry, imgs[i].element.tagName === 'BODY', imgs[i].src, imgs[i]);
+ }
+ }
+ }
+ }
+}
+
+
+function getBgElement(el) {
+ /*jshint sub: true */
+ //console.log(el.className);
+ var style = el.currentStyle || window.getComputedStyle(el, false);
+ //console.log(style.getPropertyValue('background-image'));
+ if (style.getPropertyValue('background-image') !== 'none') {
+ return {
+ element: el,
+ src: style.getPropertyValue('background-image'),
+ position: style.getPropertyValue('background-position')
+ };
+ }
+ return null;
+}
+
+
+/*function getCumulativeOffset(obj, url) {
+ var left, top;
+ left = top = 0;
+ if (obj.offsetParent) {
+ do {
+ left += obj.offsetLeft;
+ top += obj.offsetTop;
+ } while (obj = obj.offsetParent);
+ }
+ if (0 === top) {
+ left += gZeroLeft;
+ top += gZeroTop;
+ }
+ return {
+ left: left,
+ top: top,
+ };
+}*/
+
+
+function getTagImages(document) {
+ var images = document.getElementsByTagName('img'),
+ imgs = [];
+ for (var i = 0; i < images.length; i++) {
+ if (!!images[i].src) {
+ imgs.push({
+ element: images[i],
+ src: images[i].src
+ });
+ }
+ }
+ return imgs;
+}
+
+
+function heatmap(heat) {
+ function findIndex(array, predicate) {
+ var index = -1,
+ length = array ? array.length : 0;
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return array[index];
+ }
+ }
+ return array[length - 1];
+ }
+
+ return findIndex(hArr, function(chr) {
+ return heat < chr.threashold;
+ });
+}
+
+
+perfMap.init = function() {
+ startLoading();
+ // build heatmap
+ findImages();
+
+ // remove loading message
+ loading.remove();
+
+ // mouse events to move timeline around on hover
+ var elements = document.getElementsByClassName('perfmap');
+ var timeline = document.getElementById('perfmap-timeline');
+ var timelineLeft;
+ for (var i = 0, len = elements.length; i < len; i++) {
+ elements[i].onmouseover = function() {
+ timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
+ if (this.dataset.body !=='1') {
+ //this.style.opacity = 1;
+ this.style.cssText = this.style.cssText.replace(/(\d\.\d*)\)/g, '0.1)');
+ 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%;';
+ }
+
+ };
+ elements[i].onmouseout = function() {
+ timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
+ if (this.dataset.body !== '1') {
+ //this.style.opacity = 0.925;
+ this.style.cssText = this.style.cssText.replace(/(\d\.\d*)\)/g, '0.95)');
+ 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%;';
+ }
+
+ };
+ }
+};
+
+
+function placeMarker(width, height, entry, body, url, el) {
+ //background-image: linear-gradient(90deg, rgba(253, 174, 97, 0.95), rgba(253, 174, 97, 0.95));
+ var heat = (entry.responseEnd / loaded),
+ marker = document.createElement('div'),
+ padding = 9,
+ size = 18,
+ paddingTop,
+ opacity = 0.925,
+ align = 'center',
+ bodyText = '';
+
+ // adjust size of fonts/padding based on width of overlay
+ if (width < 170) {
+ padding = 12;
+ size = 12;
+ } else if (width > 400) {
+ padding = 13;
+ size = 26;
+ }
+
+ // check for overlay that matches viewport and assume it's like a background image on body
+ if ((width === document.documentElement.clientWidth) && (height >= document.documentElement.clientHeight)) {
+ body = true;
+ }
+
+ // adjust opacity if it's the body element and position label top right
+ paddingTop = (height / 2) - padding;
+ if (!!body) {
+ opacity = 0.6;
+ size = 18;
+ align = 'right';
+ paddingTop = 10;
+ bodyText = 'BODY ';
+ }
+ var elem = el.element;
+ //debugger;
+ var oldClass = elem.className;
+ elem.className = oldClass + ' perfmap';
+ elem.setAttribute('data-ms', parseInt(entry.responseEnd));
+ elem.setAttribute('data-body', (body ? 1 : 0));
+ var oldStyle = elem.style.cssText;
+ var bgImg = '';
+ if (!!el.bgImg) {
+ bgImg = ', ' + el.bgImg;
+ } else {
+ //debugger;
+ bgImg = ', url("' + elem.src + '") ';
+
+ var style = elem.currentStyle || window.getComputedStyle(elem, false);
+ //console.log(style.getPropertyValue('background-image'));
+ oldStyle += 'width: ' + (style.getPropertyValue('width') || width + 'px') + '!important;';
+ oldStyle += 'height: ' + ( style.getPropertyValue('height') || height + 'px') + '!important;';
+ elem.removeAttribute("src");
+ }
+ var bgPosition = 'background-position: 0px 0px';
+ if (!!el.position) {
+ bgPosition += ', ' + el.position;
+ }
+
+ elem.style.cssText = oldStyle + ' background-image: linear-gradient(' + heatmap(heat).rgba + ', ' + heatmap(heat).rgba + ')' + bgImg + '; ' + bgPosition + '; background-size: contain;';
+ //elem.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 + '; background: ' + heatmap(heat).value + '; top: ' + xy.top + 'px; left: ' + xy.left + 'px; width: ' + width + 'px; height:' + height + 'px; padding-top:' + paddingTop + 'px; z-index: 4000;';
+ // if (width > 50) {
+ // if (height > 15) {
+ // oldStyle = elem.style.cssText;
+ // elem.style.cssText = oldStyle + ' content: "' + bodyText + parseInt(entry.responseEnd) + 'ms (' + parseInt(entry.duration) + 'ms)";';
+ // }
+ // }
+ //document.body.appendChild(marker);
+}
+
+
+function startLoading() {
+
+ // give visual feedback asap
+ loading = document.createElement('div');
+ loading.id = 'perfmap-loading';
+ loading.innerHTML = 'Creating PerfMap';
+ 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;';
+ document.body.appendChild(loading);
+
+ // get full page load time to calculate heatmap max
+ loaded = window.performance.timing.loadEventEnd - window.performance.timing.navigationStart;
+
+ // backend
+ var backend = window.performance.timing.responseEnd - window.performance.timing.navigationStart;
+ var backendLeft = (backend / loaded) * 100;
+ var paint, firstPaint, firstPaintLeft;
+
+ // first paint in chrome from https://github.com/addyosmani/timing.js
+ if (window.chrome && window.chrome.loadTimes) {
+ paint = window.chrome.loadTimes().firstPaintTime * 1000;
+ firstPaint = paint - (window.chrome.loadTimes().startLoadTime * 1000);
+ firstPaintLeft = (firstPaint / loaded) * 100;
+ }
+
+ // remove any exisiting "perfmap" divs on second click
+ var elements = document.getElementsByClassName('perfmap');
+ while (elements.length > 0) {
+ elements[0].parentNode.removeChild(elements[0]);
+ }
+
+ // build bottom legend
+ var perfmap = document.createElement('div');
+ perfmap.id = 'perfmap';
+ 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;';
+ perfmap.innerHTML = '
Fully Loaded ' + parseInt(loaded) + 'ms
First Paint ' + parseInt(firstPaint) + 'ms
';
+ document.body.appendChild(perfmap);
+
+
+}
+
+window.perfMap = perfMap;
+})(window, document);
+perfMap.init();
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b412c19
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "perfMap",
+ "version": "0.0.1",
+ "devDependencies": {
+ "grunt": "^0.4.5",
+ "grunt-contrib-concat": "^0.5.0",
+ "grunt-contrib-jasmine": "^0.8.0",
+ "grunt-contrib-jshint": "^0.10.0",
+ "grunt-lenient": "0.0.2",
+ "grunt-wrap": "^0.3.0",
+ "phantomjs": "^1.9.11"
+ }
+}
diff --git a/perfmap.js b/perfmap.js
index 5dbb72e..c797e49 100644
--- a/perfmap.js
+++ b/perfmap.js
@@ -1,195 +1,261 @@
-var gZeroLeft = 0;
-var gZeroTop = 0;
-var gWinWidth = window.innerWidth || document.documentElement.clientWidth;
-
-function findImages() {
- var aElems = document.getElementsByTagName('*');
- var re = /url\((http.*)\)/ig;
- for ( var i=0, len = aElems.length; i < len; i++ ) {
- var elem = aElems[i];
- var style = window.getComputedStyle(elem);
- var url = elem.src || elem.href;
- var hasImage = 0;
- var fixed = 0;
- var body = 0;
- re.lastIndex = 0; // reset state of regex so we catch repeating spritesheet elements
- if (elem.tagName == 'IMG') {
- hasImage = 1;
- }
- if (style['background-image']) {
- var backgroundImage = style['background-image'];
- var matches = re.exec(style['background-image']);
- if (matches && matches.length > 1){
- url = backgroundImage.substring(4);
- url = url.substring(0, url.length - 1);
- hasImage = 1;
- if(elem.tagName == 'BODY'){
- body = 1;
- }
- }
- }
- if (style['visibility'] == "hidden") {
- hasImage = 0;
- }
- if(hasImage == 1){
- if ( url ) {
- var entry = performance.getEntriesByName(url)[0];
- if ( entry ) {
- var xy = getCumulativeOffset(elem, url);
- var wh = elem.getBoundingClientRect();
- var width = wh.width;
- var height = wh.height;
- if(width > 10){
- if(height > 10){
- placeMarker(xy, width, height, entry, body, url);
- }
- }
- }
- }
- }
- }
-}
-
-function placeMarker(xy, width, height, entry, body, url) {
- var heat = entry.responseEnd / loaded;
- // adjust size of fonts/padding based on width of overlay
- if(width < 170){
- var padding = 12;
- var size = 12;
- }else if(width > 400){
- var padding = 13;
- var size = 26;
- }else{
- var padding = 9;
- var size = 18;
- }
- // check for overlay that matches viewport and assume it's like a background image on body
- if(width == document.documentElement.clientWidth){
- if(height >= document.documentElement.clientHeight){
- body = 1;
- }
- }
- // adjust opacity if it's the body element and position label top right
- if(body == 1){
- var opacity = 0.6;
- var size = 18;
- var align = "right";
- var paddingTop = 10;
- var bodyText = "BODY ";
- }else{
- var opacity = 0.925;
- var align = "center";
- var paddingTop = (height/2)-padding;
- var bodyText = "";
- }
- var marker = document.createElement("div");
- marker.className = "perfmap";
- marker.setAttribute("data-ms", parseInt(entry.responseEnd));
- marker.setAttribute("data-body", body);
- 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;";
- if(width > 50){
- if(height > 15 ){
- marker.innerHTML = bodyText + parseInt(entry.responseEnd) + "ms (" + parseInt(entry.duration) + "ms)";
- }
- }
- document.body.appendChild(marker);
-}
+(function(window, document, undefined) {
+ 'use strict';
+ var perfMap = {};
-function heatmap(heat) {
- if ( heat < 0.16 ) {
- return "background: #1a9850;"
- }
- else if ( heat < 0.32 ) {
- return "background: #66bd63;"
- }
- else if ( heat < 0.48 ) {
- return "background: #a6d96a;"
+
+ var loading,
+ loaded,
+ gZeroLeft = 0,
+ gZeroTop = 0,
+ hArr = [{
+ threashold: 0.16,
+ value: "#1a9850"
+ }, {
+ threashold: 0.32,
+ value: "#66bd63"
+ }, {
+ threashold: 0.48,
+ value: "#a6d964"
+ }, {
+ threashold: 0.64,
+ value: "#fdae61"
+ }, {
+ threashold: 0.8,
+ value: "#f46d43"
+ }, {
+ threashold: 1.1,
+ value: "#d73027"
+ }];
+
+ function findImages() {
+ var tags = document.getElementsByTagName('*'),
+ images = document.getElementsByTagName('img'),
+ el,
+ imgs = [];
+ //re = /url\(([http].*)\)/ig;
+ //re = /(url)\((.*?)\)/ig;
+
+ function getBgElement(el) {
+ if (!!el.currentStyle) {
+ if (el.currentStyle['backgroundImage'] !== 'none') {
+ //el.className += ' bg_found';
+ return {
+ element: el,
+ src: el.currentStyle['backgroundImage']
+ };
+ }
+ } else if (!!window.getComputedStyle) {
+ if (document.defaultView.getComputedStyle(el, null).getPropertyValue('background-image') !== 'none') {
+ //el.className += ' bg_found';
+ return {
+ element: el,
+ src: document.defaultView.getComputedStyle(el, null).getPropertyValue('background-image')
+ };
+ }
+ }
+ }
+
+ for (var i = 0, len = tags.length; i < len; i++) {
+ //imgs.push(getBgElement(tags[i]));
+ var el = getBgElement(tags[i]);
+ if (!!el && !!el.src) {
+ var match = el.src.match(/\((.*?)\)/);
+ if (match[1]) {
+ el.src = match[1].replace(/('|")/g, '');
+ /*if (style['visibility'] == "hidden") {
+ hasImage = 0;
+ } else {
+ hasImage = 1;
+ if (elem.tagName == 'BODY') {
+ body = 1;
+ }
+ imgs.push(el);
+ }*/
+ imgs.push(el);
+ }
+ }
+ }
+
+ for (var i = 0; i < images.length; i++) {
+ imgs.push({
+ element: images[i],
+ src: images[i].src
+ });
+ }
+
+ //console.log(imgs);
+ for (var i = 0, len = imgs.length; i < len; i++) {
+ var entry = window.performance.getEntriesByName(imgs[i].src)[0];
+ if (entry) {
+ var xy = getCumulativeOffset(imgs[i].element, imgs[i].src);
+ var wh = imgs[i].element.getBoundingClientRect();
+ var width = wh.width;
+ var height = wh.height;
+ if (width > 10) {
+ if (height > 10) {
+ placeMarker(xy, width, height, entry, imgs[i].element.tagName === 'BODY', imgs[i].src);
+ }
+ }
+ }
+ }
}
- else if ( heat < 0.64 ) {
- return "background: #fdae61;"
+
+
+ function placeMarker(xy, width, height, entry, body, url) {
+ var heat = (entry.responseEnd / loaded),
+ marker = document.createElement("div"),
+ padding = 9,
+ size = 18,
+ paddingTop,
+ opacity = 0.925,
+ align = "center",
+ bodyText = "";
+
+ // adjust size of fonts/padding based on width of overlay
+ if (width < 170) {
+ padding = 12;
+ size = 12;
+ } else if (width > 400) {
+ padding = 13;
+ size = 26;
+ }
+
+ // check for overlay that matches viewport and assume it's like a background image on body
+ if ((width === document.documentElement.clientWidth) && (height >= document.documentElement.clientHeight)) {
+ body = true;
+ }
+
+ // adjust opacity if it's the body element and position label top right
+ paddingTop = (height / 2) - padding;
+ if (!!body) {
+ opacity = 0.6;
+ size = 18;
+ align = "right";
+ paddingTop = 10;
+ bodyText = "BODY ";
+ }
+
+ marker.className = "perfmap";
+ marker.setAttribute("data-ms", parseInt(entry.responseEnd));
+ marker.setAttribute("data-body", (body ? 1 : 0));
+ 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 + "; background: " + heatmap(heat).value + "; top: " + xy.top + "px; left: " + xy.left + "px; width: " + width + "px; height:" + height + "px; padding-top:" + paddingTop + "px; z-index: 4000;";
+ if (width > 50) {
+ if (height > 15) {
+ marker.innerHTML = bodyText + parseInt(entry.responseEnd) + "ms (" + parseInt(entry.duration) + "ms)";
+ }
+ }
+ document.body.appendChild(marker);
}
- else if ( heat < 0.8 ) {
- return "background: #f46d43;"
- }else{
- return "background: #d73027;"
+
+ function heatmap(heat) {
+ function findIndex(array, predicate) {
+ var index = -1,
+ length = array ? array.length : 0;
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return array[index];
+ }
+ }
+ return array[length - 1];
+ }
+
+ return findIndex(hArr, function(chr) {
+ return heat < chr.threashold;
+ });
}
-}
-
-function getCumulativeOffset(obj, url) {
- var left, top;
- left = top = 0;
- if (obj.offsetParent) {
- do {
- left += obj.offsetLeft;
- top += obj.offsetTop;
- } while (obj = obj.offsetParent);
+
+ function getCumulativeOffset(obj, url) {
+ var left, top;
+ left = top = 0;
+ if (obj.offsetParent) {
+ do {
+ left += obj.offsetLeft;
+ top += obj.offsetTop;
+ } while (obj = obj.offsetParent);
+ }
+ if (0 === top) {
+ left += gZeroLeft;
+ top += gZeroTop;
+ }
+ return {
+ left: left,
+ top: top,
+ };
}
- if ( 0 == top ) {
- left += gZeroLeft;
- top += gZeroTop;
+
+ function startLoading() {
+
+ // give visual feedback asap
+ loading = document.createElement("div");
+ loading.id = "perfmap-loading";
+ loading.innerHTML = "Creating PerfMap";
+ 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;";
+ document.body.appendChild(loading);
+
+ // get full page load time to calculate heatmap max
+ loaded = window.performance.timing.loadEventEnd - window.performance.timing.navigationStart;
+
+ // backend
+ var backend = window.performance.timing.responseEnd - window.performance.timing.navigationStart;
+ var backendLeft = (backend / loaded) * 100;
+ var paint, firstPaint, firstPaintLeft;
+
+ // first paint in chrome from https://github.com/addyosmani/timing.js
+ if (window.chrome && window.chrome.loadTimes) {
+ paint = window.chrome.loadTimes().firstPaintTime * 1000;
+ firstPaint = paint - (window.chrome.loadTimes().startLoadTime * 1000);
+ firstPaintLeft = (firstPaint / loaded) * 100;
+ }
+
+ // remove any exisiting "perfmap" divs on second click
+ var elements = document.getElementsByClassName("perfmap");
+ while (elements.length > 0) {
+ elements[0].parentNode.removeChild(elements[0]);
+ }
+
+ // build bottom legend
+ var perfmap = document.createElement("div");
+ perfmap.id = "perfmap";
+ 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;";
+ perfmap.innerHTML = "Fully Loaded " + parseInt(loaded) + "ms
First Paint " + parseInt(firstPaint) + "ms
";
+ document.body.appendChild(perfmap);
+
+
}
- return {
- left: left,
- top: top,
+
+
+
+ perfMap.init = function() {
+ startLoading();
+ // build heatmap
+ findImages();
+
+ // remove loading message
+ loading.remove();
+
+ // mouse events to move timeline around on hover
+ var elements = document.getElementsByClassName("perfmap");
+ var timeline = document.getElementById('perfmap-timeline');
+ var timelineLeft;
+ for (var i = 0, len = elements.length; i < len; i++) {
+ elements[i].onmouseover = function() {
+ timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
+ if (this.dataset.body != "1") {
+ this.style.opacity = 1;
+ }
+ 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%;";
+ };
+ elements[i].onmouseout = function() {
+ timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
+ if (this.dataset.body != "1") {
+ this.style.opacity = 0.925;
+ }
+ 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%;";
+ };
+ }
};
-}
-
-// give visual feedback asap
-var loading = document.createElement("div");
-loading.id = "perfmap-loading";
-loading.innerHTML = "Creating PerfMap";
-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;";
-document.body.appendChild(loading);
-
-// get full page load time to calculate heatmap max
-var loaded = performance.timing.loadEventEnd - performance.timing.navigationStart;
-
-// backend
-var backend = performance.timing.responseEnd - performance.timing.navigationStart;
-var backendLeft = (backend / loaded)*100;
-
-// first paint in chrome from https://github.com/addyosmani/timing.js
-if (window.chrome && window.chrome.loadTimes) {
- var paint = window.chrome.loadTimes().firstPaintTime * 1000;
- var firstPaint = paint - (window.chrome.loadTimes().startLoadTime*1000);
- var firstPaintLeft = (firstPaint / loaded)*100;
-}
-
-// remove any exisiting "perfmap" divs on second click
-var elements = document.getElementsByClassName("perfmap");
-while(elements.length > 0){
- elements[0].parentNode.removeChild(elements[0]);
-}
-
-// build bottom legend
-var perfmap = document.createElement("div");
-perfmap.id = "perfmap";
-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;";
-perfmap.innerHTML = "Fully Loaded " + parseInt(loaded) + "ms
First Paint " + parseInt(firstPaint) + "ms
";
-document.body.appendChild(perfmap);
-
-// build heatmap
-findImages();
-
-// remove loading message
-loading.remove();
-
-// mouse events to move timeline around on hover
-var elements = document.getElementsByClassName("perfmap");
-var timeline = document.getElementById('perfmap-timeline');
-for ( var i=0, len = elements.length; i < len; i++ ) {
- elements[i].onmouseover = function(){
- var timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
- if(this.dataset.body != "1"){
- this.style.opacity = 1;
- }
- 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%;";
- }
- elements[i].onmouseout = function(){
- var timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
- if(this.dataset.body != "1"){
- this.style.opacity = 0.925;
- }
- 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%;";
- }
-}
\ No newline at end of file
+
+ window.perfMap = perfMap;
+})(window, document);
+
+perfMap.init();
\ No newline at end of file
diff --git a/specs/getBgImages.spec.js b/specs/getBgImages.spec.js
new file mode 100644
index 0000000..eba102b
--- /dev/null
+++ b/specs/getBgImages.spec.js
@@ -0,0 +1,80 @@
+'use strict';
+describe('getBgElement spec', function() {
+ var dummyDOM = '' +
+ '

' +
+ '

' +
+ '

' +
+ '
' +
+ '
';
+
+
+
+ it("should return 1 element with same src url", function() {
+ var div = document.createElement("div");
+ div.style.width = "100px";
+ div.style.height = "100px";
+ div.style.background = "url(http://somePath.com/jasmine.jpg)";
+ div.style.color = "white";
+ div.innerHTML = "Hello";
+
+ document.body.appendChild(div);
+
+ var result = getBgElement(div);
+ expect(result.element).toEqual(div);
+ expect(result.src).toEqual(div.style.background);
+ });
+
+ it("should return null when has backgroung-image = none", function() {
+ var div = document.createElement("div");
+ div.style.width = "100px";
+ div.style.height = "100px";
+ div.style.background = "none";
+ div.style.color = "white";
+ div.innerHTML = "Hello";
+
+ document.body.appendChild(div);
+
+ var result = getBgElement(div);
+ expect(result).toEqual(null);
+ });
+ it("should return null when has no backgroung", function() {
+ var div = document.createElement("div");
+ div.style.width = "100px";
+ div.style.height = "100px";
+
+ div.style.color = "white";
+ div.innerHTML = "Hello";
+
+ document.body.appendChild(div);
+
+ var result = getBgElement(div);
+ expect(result).toEqual(null);
+ });
+
+ it("should return null when has no backgroung", function() {
+ var css = '.gb_Fa { background-image: url("https://somePath.com/i1_e9f91fe3.png"); background-size: 247px 204px;}',
+ head = document.head || document.getElementsByTagName('head')[0],
+ style = document.createElement('style');
+ style.type = 'text/css';
+ if (style.styleSheet) {
+ style.styleSheet.cssText = css;
+ } else {
+ style.appendChild(document.createTextNode(css));
+ }
+
+ head.appendChild(style);
+
+
+ var div = document.createElement("div");
+ div.className = "gb_Fa";
+
+
+ document.body.appendChild(div);
+
+ var result = getBgElement(div);
+ expect(result.element).toEqual(div);
+ expect(result.src).toEqual('url(https://somePath.com/i1_e9f91fe3.png)');
+ });
+
+
+});
\ No newline at end of file
diff --git a/specs/getTagImages.spec.js b/specs/getTagImages.spec.js
new file mode 100644
index 0000000..8e80f53
--- /dev/null
+++ b/specs/getTagImages.spec.js
@@ -0,0 +1,54 @@
+'use strict';
+describe('getTagImages spec', function() {
+ var container = document.createElement("div");
+ container.id = 'container';
+ document.body.appendChild(container);
+
+ beforeEach(function() {
+ document.getElementById('container');
+ container.innerHTML = '';
+ });
+
+ it("should return 3 images", function() {
+ container.innerHTML = '' +
+ '

' +
+ '

' +
+ '

' +
+ '
' +
+ '
';
+ var result = getTagImages(document);
+ expect(result.length).toEqual(3);
+ });
+
+ it("should return 0 images", function() {
+ var div = document.createElement("div");
+ container.appendChild(div);
+ var result = getTagImages(document);
+ expect(result).toEqual([]);
+ });
+
+ it("should return 3 images with different extensions", function() {
+ container.innerHTML = '' +
+ '

' +
+ '

' +
+ '

' +
+ '
' +
+ '
';
+ var result = getTagImages(document);
+ expect(result[0].src).toEqual("http://somePath.com/jasmine.png");
+ expect(result[1].src).toEqual("http://somePath.com/jasmine.jpg");
+ expect(result[2].src).toEqual("http://somePath.com/jasmine.gif");
+ });
+
+ it("should return 0 images when it has no src attribute ", function() {
+ container.innerHTML = '' +
+ '
![abc some alt]()
' +
+ '
' +
+ '
';
+ var result = getTagImages(document);
+ expect(result.length).toEqual(0);
+ });
+
+
+
+});
\ No newline at end of file
diff --git a/src/_vars.js b/src/_vars.js
new file mode 100644
index 0000000..36f36a0
--- /dev/null
+++ b/src/_vars.js
@@ -0,0 +1,30 @@
+var perfMap = {},
+ loading,
+ loaded,
+ gZeroLeft = 0,
+ gZeroTop = 0,
+ hArr = [{
+ threashold: 0.16,
+ value: '#1a9850',
+ rgba: 'rgba(26, 152, 80, 0.95)'
+ }, {
+ threashold: 0.32,
+ value: '#66bd63',
+ rgba: 'rgba(102, 189, 99, 0.95)'
+ }, {
+ threashold: 0.48,
+ value: '#a6d964',
+ rgba: 'rgba(166, 217, 100, 0.95)'
+ }, {
+ threashold: 0.64,
+ value: '#fdae61',
+ rgba: 'rgba(253, 174, 97, 0.95)'
+ }, {
+ threashold: 0.8,
+ value: '#f46d43',
+ rgba: 'rgba(244, 109, 67, 0.95)'
+ }, {
+ threashold: 1.1,
+ value: '#d73027',
+ rgba: 'rgba(215, 48, 39, 0.95)'
+ }];
\ No newline at end of file
diff --git a/src/findImages.js b/src/findImages.js
new file mode 100644
index 0000000..ef6e474
--- /dev/null
+++ b/src/findImages.js
@@ -0,0 +1,53 @@
+'use strict';
+
+function findImages() {
+ var tags = document.getElementsByTagName('*'),
+ images = document.getElementsByTagName('img'),
+ el,
+ len,
+ imgs = [];
+ //re = /url\(([http].*)\)/ig;
+ //re = /(url)\((.*?)\)/ig;
+
+ imgs = getTagImages(document);
+ len = tags.length;
+ for (var j = 0; j < len; j++) {
+ el = getBgElement(tags[j]);
+ if (!!el && !!el.src) {
+ el.bgImg = el.src;
+
+ var match = el.src.match(/\((.*?)\)/);
+ if (match[1]) {
+ el.src = match[1].replace(/('|")/g, '');
+ /*if (style['visibility'] == "hidden") {
+ hasImage = 0;
+ } else {
+ hasImage = 1;
+ if (elem.tagName == 'BODY') {
+ body = 1;
+ }
+ imgs.push(el);
+ }*/
+ imgs.push(el);
+ }
+ }
+ }
+
+
+ //console.log(imgs);
+ len = imgs.length;
+ for (var i = 0; i < len; i++) {
+ var entry = window.performance.getEntriesByName(imgs[i].src)[0];
+ if (entry) {
+ //var xy = getCumulativeOffset(imgs[i].element, imgs[i].src);
+ var wh = imgs[i].element.getBoundingClientRect();
+ var width = wh.width;
+ var height = wh.height;
+ if (width > 10) {
+ if (height > 10) {
+ placeMarker(width, height, entry, imgs[i].element.tagName === 'BODY', imgs[i].src, imgs[i]);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/getBgImages.js b/src/getBgImages.js
new file mode 100644
index 0000000..b3b2825
--- /dev/null
+++ b/src/getBgImages.js
@@ -0,0 +1,16 @@
+'use strict';
+
+function getBgElement(el) {
+ /*jshint sub: true */
+ //console.log(el.className);
+ var style = el.currentStyle || window.getComputedStyle(el, false);
+ //console.log(style.getPropertyValue('background-image'));
+ if (style.getPropertyValue('background-image') !== 'none') {
+ return {
+ element: el,
+ src: style.getPropertyValue('background-image'),
+ position: style.getPropertyValue('background-position')
+ };
+ }
+ return null;
+}
\ No newline at end of file
diff --git a/src/getCumulativeOffset.js b/src/getCumulativeOffset.js
new file mode 100644
index 0000000..9bb0ddb
--- /dev/null
+++ b/src/getCumulativeOffset.js
@@ -0,0 +1,20 @@
+'use strict';
+
+/*function getCumulativeOffset(obj, url) {
+ var left, top;
+ left = top = 0;
+ if (obj.offsetParent) {
+ do {
+ left += obj.offsetLeft;
+ top += obj.offsetTop;
+ } while (obj = obj.offsetParent);
+ }
+ if (0 === top) {
+ left += gZeroLeft;
+ top += gZeroTop;
+ }
+ return {
+ left: left,
+ top: top,
+ };
+}*/
\ No newline at end of file
diff --git a/src/getTagImages.js b/src/getTagImages.js
new file mode 100644
index 0000000..deb3fb7
--- /dev/null
+++ b/src/getTagImages.js
@@ -0,0 +1,15 @@
+'use strict';
+
+function getTagImages(document) {
+ var images = document.getElementsByTagName('img'),
+ imgs = [];
+ for (var i = 0; i < images.length; i++) {
+ if (!!images[i].src) {
+ imgs.push({
+ element: images[i],
+ src: images[i].src
+ });
+ }
+ }
+ return imgs;
+}
\ No newline at end of file
diff --git a/src/heatmap.js b/src/heatmap.js
new file mode 100644
index 0000000..2d27fcb
--- /dev/null
+++ b/src/heatmap.js
@@ -0,0 +1,18 @@
+'use strict';
+
+function heatmap(heat) {
+ function findIndex(array, predicate) {
+ var index = -1,
+ length = array ? array.length : 0;
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return array[index];
+ }
+ }
+ return array[length - 1];
+ }
+
+ return findIndex(hArr, function(chr) {
+ return heat < chr.threashold;
+ });
+}
\ No newline at end of file
diff --git a/src/init.js b/src/init.js
new file mode 100644
index 0000000..8fdca81
--- /dev/null
+++ b/src/init.js
@@ -0,0 +1,35 @@
+'use strict';
+
+perfMap.init = function() {
+ startLoading();
+ // build heatmap
+ findImages();
+
+ // remove loading message
+ loading.remove();
+
+ // mouse events to move timeline around on hover
+ var elements = document.getElementsByClassName('perfmap');
+ var timeline = document.getElementById('perfmap-timeline');
+ var timelineLeft;
+ for (var i = 0, len = elements.length; i < len; i++) {
+ elements[i].onmouseover = function() {
+ timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
+ if (this.dataset.body !=='1') {
+ //this.style.opacity = 1;
+ this.style.cssText = this.style.cssText.replace(/(\d\.\d*)\)/g, '0.1)');
+ 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%;';
+ }
+
+ };
+ elements[i].onmouseout = function() {
+ timelineLeft = document.documentElement.clientWidth * (this.dataset.ms / loaded);
+ if (this.dataset.body !== '1') {
+ //this.style.opacity = 0.925;
+ this.style.cssText = this.style.cssText.replace(/(\d\.\d*)\)/g, '0.95)');
+ 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%;';
+ }
+
+ };
+ }
+};
\ No newline at end of file
diff --git a/src/placeMarker.js b/src/placeMarker.js
new file mode 100644
index 0000000..723c293
--- /dev/null
+++ b/src/placeMarker.js
@@ -0,0 +1,71 @@
+'use strict';
+
+function placeMarker(width, height, entry, body, url, el) {
+ //background-image: linear-gradient(90deg, rgba(253, 174, 97, 0.95), rgba(253, 174, 97, 0.95));
+ var heat = (entry.responseEnd / loaded),
+ marker = document.createElement('div'),
+ padding = 9,
+ size = 18,
+ paddingTop,
+ opacity = 0.925,
+ align = 'center',
+ bodyText = '';
+
+ // adjust size of fonts/padding based on width of overlay
+ if (width < 170) {
+ padding = 12;
+ size = 12;
+ } else if (width > 400) {
+ padding = 13;
+ size = 26;
+ }
+
+ // check for overlay that matches viewport and assume it's like a background image on body
+ if ((width === document.documentElement.clientWidth) && (height >= document.documentElement.clientHeight)) {
+ body = true;
+ }
+
+ // adjust opacity if it's the body element and position label top right
+ paddingTop = (height / 2) - padding;
+ if (!!body) {
+ opacity = 0.6;
+ size = 18;
+ align = 'right';
+ paddingTop = 10;
+ bodyText = 'BODY ';
+ }
+ var elem = el.element;
+ //debugger;
+ var oldClass = elem.className;
+ elem.className = oldClass + ' perfmap';
+ elem.setAttribute('data-ms', parseInt(entry.responseEnd));
+ elem.setAttribute('data-body', (body ? 1 : 0));
+ var oldStyle = elem.style.cssText;
+ var bgImg = '';
+ if (!!el.bgImg) {
+ bgImg = ', ' + el.bgImg;
+ } else {
+ //debugger;
+ bgImg = ', url("' + elem.src + '") ';
+
+ var style = elem.currentStyle || window.getComputedStyle(elem, false);
+ //console.log(style.getPropertyValue('background-image'));
+ oldStyle += 'width: ' + (style.getPropertyValue('width') || width + 'px') + '!important;';
+ oldStyle += 'height: ' + ( style.getPropertyValue('height') || height + 'px') + '!important;';
+ elem.removeAttribute("src");
+ }
+ var bgPosition = 'background-position: 0px 0px';
+ if (!!el.position) {
+ bgPosition += ', ' + el.position;
+ }
+
+ elem.style.cssText = oldStyle + ' background-image: linear-gradient(' + heatmap(heat).rgba + ', ' + heatmap(heat).rgba + ')' + bgImg + '; ' + bgPosition + '; background-size: contain;';
+ //elem.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 + '; background: ' + heatmap(heat).value + '; top: ' + xy.top + 'px; left: ' + xy.left + 'px; width: ' + width + 'px; height:' + height + 'px; padding-top:' + paddingTop + 'px; z-index: 4000;';
+ // if (width > 50) {
+ // if (height > 15) {
+ // oldStyle = elem.style.cssText;
+ // elem.style.cssText = oldStyle + ' content: "' + bodyText + parseInt(entry.responseEnd) + 'ms (' + parseInt(entry.duration) + 'ms)";';
+ // }
+ // }
+ //document.body.appendChild(marker);
+}
\ No newline at end of file
diff --git a/src/startLoading.js b/src/startLoading.js
new file mode 100644
index 0000000..948c297
--- /dev/null
+++ b/src/startLoading.js
@@ -0,0 +1,41 @@
+'use strict';
+
+function startLoading() {
+
+ // give visual feedback asap
+ loading = document.createElement('div');
+ loading.id = 'perfmap-loading';
+ loading.innerHTML = 'Creating PerfMap';
+ 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;';
+ document.body.appendChild(loading);
+
+ // get full page load time to calculate heatmap max
+ loaded = window.performance.timing.loadEventEnd - window.performance.timing.navigationStart;
+
+ // backend
+ var backend = window.performance.timing.responseEnd - window.performance.timing.navigationStart;
+ var backendLeft = (backend / loaded) * 100;
+ var paint, firstPaint, firstPaintLeft;
+
+ // first paint in chrome from https://github.com/addyosmani/timing.js
+ if (window.chrome && window.chrome.loadTimes) {
+ paint = window.chrome.loadTimes().firstPaintTime * 1000;
+ firstPaint = paint - (window.chrome.loadTimes().startLoadTime * 1000);
+ firstPaintLeft = (firstPaint / loaded) * 100;
+ }
+
+ // remove any exisiting "perfmap" divs on second click
+ var elements = document.getElementsByClassName('perfmap');
+ while (elements.length > 0) {
+ elements[0].parentNode.removeChild(elements[0]);
+ }
+
+ // build bottom legend
+ var perfmap = document.createElement('div');
+ perfmap.id = 'perfmap';
+ 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;';
+ perfmap.innerHTML = 'Fully Loaded ' + parseInt(loaded) + 'ms
First Paint ' + parseInt(firstPaint) + 'ms
';
+ document.body.appendChild(perfmap);
+
+
+}
\ No newline at end of file