I need to adjust image sizes in a lazy loading mechanism. The final image paths are stored in attribute data-src, if lazy loading is enabled.
For optical reasons, I don't want to wait until lazy loading is done, so I've tried the following:
window.addEventListener('load', function() {
const aImages = document.getElementsByClassName('article-image');
for ( var i=0;i<aImages.length;i++ ) {
const wrp = aImages[i].parentElement;
var imgHeight, imgWidth;
// if lazyload
if ( aImages[i].getAttribute('data-src') ) {
var img = new Image();
img.onload = function() { imgHeight = this.naturalHeight; imgWidth = this.naturalWidth };
img.src = aImages[i].getAttribute('data-src');
} else {
imgHeight = aImages[i].naturalHeight;
imgWidth = aImages[i].naturalWidth;
}
var dim = calcAspectRatioFit(imgWidth, imgHeight, wrp.clientWidth, wrp.clientHeight);
console.log('['+i+']\n' + 'dim.width: '+dim.width + '\ndim.height: '+dim.height + '\ntypeof(wrp): '+typeof(wrp) + '\nwrp.clientWidth: '+wrp.clientWidth + '\nwrp.clientHeight: '+wrp.clientHeight + '\nimgWidth: '+imgWidth + '\nimgHeight: '+imgHeight);
aImages[i].style.setProperty('width', dim.width + 'px', 'important');
aImages[i].style.setProperty('height', dim.height + 'px', 'imoprtant');
}
});
Guess the problem is the onload-function. If I set a breakpoint between it and before calcAspectRatioFit(), everything works as excepted. Guess 'img' then has enough time to load.
Debug-Output from console.log():
[0]
dim.width: NaN
dim.height: NaN
typeof(wrp): object
wrp.clientWidth: 222
wrp.clientHeight: 192
imgWidth: undefined
imgHeight: undefined
Any ideas how to solve this?
// Edit 2:
Of course, the problem was, that the last four lines didn't wait for img.onload() to be executed. Thanks #Peter Krebs for this hint.
Here is a QnD solution which works fine. (Added anonymous wrapper fnc to copy i)
window.addEventListener('load', function() {
const aImages = document.getElementsByClassName('article-image');
const exec = function(i) {
const wrp = aImages[i].parentElement;
var dim = calcAspectRatioFit(aImages[i].naturalWidth, aImages[i].naturalHeight, wrp.clientWidth, wrp.clientHeight);
aImages[i].style.setProperty('width', dim.width + 'px');
aImages[i].style.setProperty('height', dim.height + 'px');
}
for ( var i=0;i<aImages.length;i++ ) {
if ( aImages[i].getAttribute('data-src') ) {
aImages[i].onload = (function(i) { return function() { exec(i) } })(i);
this.src = aImages[i].getAttribute('data-src');
} else { exec(i) }
}
});
Related
I have a function that adds random images to my divs that disappear over time and create a mouse trail. My issue is: there are double images.
I'd like to add something that checks if the background url is already located on the page and if that's the case, skips to the next in the array and check it again until until it comes across one that is not there. So like a loop that refreshes every time a 'trail' is being created.
Maybe I am asking for something that won't work? What if all the images are already on the page? I don't really have an answer for it and I also don't have an idea how to solve that problem yet.
For now I tried adding a counter that checks the usedImages and counts them, but it seems to have flaws and I am unsure where to look or how to fix it. Does anyone have any tips on how to do this? Is it even possible?
My fiddle
var bgImages = new Array(
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/05/schraegluftbild-1024x725.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/05/pikto-608x1024.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/05/Jettenhausen-EG-Grundriss-913x1024.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/05/lageplan-945x1024.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/05/Jettenhausen-EG-Grundriss-913x1024.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/05/Jettenhauser-Esch-Modell-2-1024x768.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/07/DSCN3481-1024x768.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2019/07/zoom-in-3_ASTOC-1024x683.jpg",
"https://www.studiourbanestrategien.com/wordpress/wp-content/uploads/2016/07/IMG_1345-1024x768.jpg"
);
const numberOfImages = 10;
const timesPerSecond = .1;
var usedImages = {};
var usedImagesCount = 0;
function preloadImages(images) {
for (i = 0; i < images.length; i++) {
let l = document.createElement('link')
l.rel = 'preload'
l.as = 'image'
l.href = images[i]
document.head.appendChild(l);
}
}
function animate(e) {
var image = document.createElement('div');
image.classList.add('trail');
var sizew = 200;
var sizeh = 200;
image.style.transition = '3s ease';
image.style.position = 'fixed';
image.style.top = e.pageY - sizeh / 2 + 'px';
image.style.left = e.pageX - sizew / 2 + 'px';
image.style.width = sizew + 'px';
image.style.height = sizeh + 'px';
var random = Math.floor(Math.random() * (bgImages.length));
if (!usedImages[random]) {
image.style.backgroundImage = "url(" + bgImages[random] + ")";
usedImages[random] = true;
usedImagesCount++;
if (usedImagesCount === bgImages.length) {
usedImagesCount = 0;
usedImages = {};
}
} else {
animate(e);
}
console.log(usedImages);
console.log(usedImagesCount);
image.style.backgroundSize = 'cover';
image.style.pointerEvents = 'none';
image.style.zIndex = 1;
document.body.appendChild(image);
//opacity and blur animations
window.setTimeout(function() {
image.style.opacity = 0;
image.style.filter = 'blur(20px)';
}, 40);
window.setTimeout(function() {
document.body.removeChild(image);
}, 2100);
};
window.onload = function() {
preloadImages(bgImages);
var wait = false;
window.addEventListener('mousemove', function(e) {
if (!wait) {
wait = true;
setTimeout(() => {
wait = false
}, timesPerSecond * 800);
animate(e);
}
});
};
I cannot get cloudzoom to work with my woocommerce site, every time I choose an attribute ie size the main image disappears, it is like cloudzoom is trying to force an attribute image. I am absolutely pants at javascript and looking through the file I cannot see where I need to edit or delete the code. Any help would be greatly appreciated, I don't need the atribute image function to work at all so just stripping out the code would suit me. Here is the code...
(function ($) {
$(document).ready(function () {
$('.cloud-zoom, .cloud-zoom-gallery').CloudZoom();
});
function format(str) {
for (var i = 1; i < arguments.length; i++) {
str = str.replace('%' + (i - 1), arguments[i]);
}
return str;
}
function CloudZoom(jWin, opts) {
var sImg = $('img', jWin);
var img1;
var img2;
var zoomDiv = null;
var $mouseTrap = null;
var lens = null;
var $tint = null;
var softFocus = null;
var $ie6Fix = null;
var zoomImage;
var controlTimer = 0;
var cw, ch;
var destU = 0;
var destV = 0;
var currV = 0;
var currU = 0;
var filesLoaded = 0;
var mx,
my;
var ctx = this, zw;
// Display an image loading message. This message gets deleted when the images have loaded and the zoom init function is called.
// We add a small delay before the message is displayed to avoid the message flicking on then off again virtually immediately if the
// images load really fast, e.g. from the cache.
//var ctx = this;
setTimeout(function () {
// <img src="/images/loading.gif"/>
if ($mouseTrap === null) {
var w = jWin.width();
jWin.parent().append(format('<div style="width:%0px;position:absolute;top:75%;left:%1px;text-align:center" class="cloud-zoom-loading" >Loading...</div>', w / 3, (w / 2) - (w / 6))).find(':last').css('opacity', 0.5);
}
}, 200);
var ie6FixRemove = function () {
if ($ie6Fix !== null) {
$ie6Fix.remove();
$ie6Fix = null;
}
};
// Removes cursor, tint layer, blur layer etc.
this.removeBits = function () {
//$mouseTrap.unbind();
if (lens) {
lens.remove();
lens = null;
}
if ($tint) {
$tint.remove();
$tint = null;
}
if (softFocus) {
softFocus.remove();
softFocus = null;
}
ie6FixRemove();
$('.cloud-zoom-loading', jWin.parent()).remove();
};
this.destroy = function () {
jWin.data('zoom', null);
if ($mouseTrap) {
$mouseTrap.unbind();
$mouseTrap.remove();
$mouseTrap = null;
}
if (zoomDiv) {
zoomDiv.remove();
zoomDiv = null;
}
//ie6FixRemove();
this.removeBits();
// DON'T FORGET TO REMOVE JQUERY 'DATA' VALUES
};
// This is called when the zoom window has faded out so it can be removed.
this.fadedOut = function () {
if (zoomDiv) {
zoomDiv.remove();
zoomDiv = null;
}
this.removeBits();
//ie6FixRemove();
};
this.controlLoop = function () {
if (lens) {
var x = (mx - sImg.offset().left - (cw * 0.5)) >> 0;
var y = (my - sImg.offset().top - (ch * 0.5)) >> 0;
if (x < 0) {
x = 0;
}
else if (x > (sImg.outerWidth() - cw)) {
x = (sImg.outerWidth() - cw);
}
if (y < 0) {
y = 0;
}
else if (y > (sImg.outerHeight() - ch)) {
y = (sImg.outerHeight() - ch);
}
lens.css({
left: x,
top: y
});
lens.css('background-position', (-x) + 'px ' + (-y) + 'px');
destU = (((x) / sImg.outerWidth()) * zoomImage.width) >> 0;
destV = (((y) / sImg.outerHeight()) * zoomImage.height) >> 0;
currU += (destU - currU) / opts.smoothMove;
currV += (destV - currV) / opts.smoothMove;
zoomDiv.css('background-position', (-(currU >> 0) + 'px ') + (-(currV >> 0) + 'px'));
}
controlTimer = setTimeout(function () {
ctx.controlLoop();
}, 30);
};
this.init2 = function (img, id) {
filesLoaded++;
//console.log(img.src + ' ' + id + ' ' + img.width);
if (id === 1) {
zoomImage = img;
}
//this.images[id] = img;
if (filesLoaded === 2) {
this.init();
}
};
/* Init function start. */
this.init = function () {
// Remove loading message (if present);
$('.cloud-zoom-loading', jWin.parent()).remove();
/* Add a box (mouseTrap) over the small image to trap mouse events.
It has priority over zoom window to avoid issues with inner zoom.
We need the dummy background image as IE does not trap mouse events on
transparent parts of a div.
*/
$mouseTrap = jWin.parent().append(format("<div class='mousetrap' style='background-image:url(\".\");z-index:3;position:absolute;width:%0px;height:%1px;left:%2px;top:%3px;\'></div>", sImg.outerWidth(), sImg.outerHeight(), 0, 0)).find(':last');
//////////////////////////////////////////////////////////////////////
/* Do as little as possible in mousemove event to prevent slowdown. */
$mouseTrap.bind('mousemove', this, function (event) {
// Just update the mouse position
mx = event.pageX;
my = event.pageY;
});
//////////////////////////////////////////////////////////////////////
$mouseTrap.bind('mouseleave', this, function (event) {
clearTimeout(controlTimer);
//event.data.removeBits();
if(lens) { lens.fadeOut(299); }
if($tint) { $tint.fadeOut(299); }
if(softFocus) { softFocus.fadeOut(299); }
zoomDiv.fadeOut(300, function () {
ctx.fadedOut();
});
return false;
});
//////////////////////////////////////////////////////////////////////
$mouseTrap.bind('mouseenter', this, function (event) {
mx = event.pageX;
my = event.pageY;
zw = event.data;
if (zoomDiv) {
zoomDiv.stop(true, false);
zoomDiv.remove();
}
var xPos = opts.adjustX,
yPos = opts.adjustY;
var siw = sImg.outerWidth();
var sih = sImg.outerHeight();
var w = opts.zoomWidth;
var h = opts.zoomHeight;
if (opts.zoomWidth == 'auto') {
w = siw;
}
if (opts.zoomHeight == 'auto') {
h = sih;
}
//$('#info').text( xPos + ' ' + yPos + ' ' + siw + ' ' + sih );
var appendTo = jWin.parent(); // attach to the wrapper
switch (opts.position) {
case 'top':
yPos -= h; // + opts.adjustY;
break;
case 'right':
xPos += siw; // + opts.adjustX;
break;
case 'bottom':
yPos += sih; // + opts.adjustY;
break;
case 'left':
xPos -= w; // + opts.adjustX;
break;
case 'inside':
w = siw;
h = sih;
break;
// All other values, try and find an id in the dom to attach to.
default:
appendTo = $('#' + opts.position);
// If dom element doesn't exit, just use 'right' position as default.
if (!appendTo.length) {
appendTo = jWin;
xPos += siw; //+ opts.adjustX;
yPos += sih; // + opts.adjustY;
} else {
w = appendTo.innerWidth();
h = appendTo.innerHeight();
}
}
zoomDiv = appendTo.append(format('<div id="cloud-zoom-big" class="cloud-zoom-big" style="display:none;position:absolute;left:%0px;top:%1px;width:%2px;height:%3px;background-image:url(\'%4\');z-index:2;"></div>', xPos, yPos, w, h, zoomImage.src)).find(':last');
// Add the title from title tag.
if (sImg.attr('title') && opts.showTitle) {
zoomDiv.append(format('<div class="cloud-zoom-title">%0</div>', sImg.attr('title'))).find(':last').css('opacity', opts.titleOpacity);
}
// Fix ie6 select elements wrong z-index bug. Placing an iFrame over the select element solves the issue...
if ($.browser.msie && $.browser.version < 7) {
$ie6Fix = $('<iframe frameborder="0" src="#"></iframe>').css({
position: "absolute",
left: xPos,
top: yPos,
zIndex: 99,
width: w,
height: h
}).insertBefore(zoomDiv);
}
zoomDiv.fadeIn(500);
if (lens) {
lens.remove();
lens = null;
} /* Work out size of cursor */
cw = (sImg.outerWidth() / zoomImage.width) * zoomDiv.width();
ch = (sImg.outerHeight() / zoomImage.height) * zoomDiv.height();
// Attach mouse, initially invisible to prevent first frame glitch
lens = jWin.append(format("<div class = 'cloud-zoom-lens' style='display:none;z-index:1;position:absolute;width:%0px;height:%1px;opacity:0.4'></div>", cw, ch)).find(':last');
$mouseTrap.css('cursor', lens.css('cursor'));
var noTrans = false;
// Init tint layer if needed. (Not relevant if using inside mode)
if (opts.tint) {
/* lens.css('background', 'url("' + sImg.attr('src') + '")'); */
$tint = jWin.append(format('<div style="display:none;position:absolute; left:0px; top:0px; width:%0px; height:%1px; background-color:%2;" />', sImg.outerWidth(), sImg.outerHeight(), opts.tint)).find(':last');
$tint.css('opacity', opts.tintOpacity);
noTrans = true;
$tint.fadeIn(500);
}
if (opts.softFocus) {
lens.css('background', 'url("' + sImg.attr('src') + '")');
softFocus = jWin.append(format('<div style="position:absolute;display:none;top:2px; left:2px; width:%0px; height:%1px;" />', sImg.outerWidth() - 2, sImg.outerHeight() - 2, opts.tint)).find(':last');
softFocus.css('background', 'url("' + sImg.attr('src') + '")');
softFocus.css('opacity', 0.5);
noTrans = true;
softFocus.fadeIn(500);
}
if (!noTrans) {
lens.css('opacity', opts.lensOpacity);
}
if ( opts.position !== 'inside' ) { lens.fadeIn(500); }
// Start processing.
zw.controlLoop();
return; // Don't return false here otherwise opera will not detect change of the mouse pointer type.
});
};
img1 = new Image();
$(img1).load(function () {
ctx.init2(this, 0);
});
img1.src = sImg.attr('src');
img2 = new Image();
$(img2).load(function () {
ctx.init2(this, 1);
});
img2.src = jWin.attr('href');
}
$.fn.CloudZoom = function (options) {
// IE6 background image flicker fix
try {
document.execCommand("BackgroundImageCache", false, true);
} catch (e) {}
this.each(function () {
var relOpts, opts;
// Hmm...eval...slap on wrist.
eval('var a = {' + $(this).attr('rel') + '}');
relOpts = a;
if ($(this).is('.cloud-zoom')) {
$(this).css({
'position': 'relative',
'display': 'block'
});
$('img', $(this)).css({
'display': 'block'
});
// Wrap an outer div around the link so we can attach things without them becoming part of the link.
// But not if wrap already exists.
if ($(this).parent().attr('id') != 'wrap') {
$(this).wrap('<div id="wrap" style="top:0px;z-index:4;position:relative;"></div>');
}
opts = $.extend({}, $.fn.CloudZoom.defaults, options);
opts = $.extend({}, opts, relOpts);
$(this).data('zoom', new CloudZoom($(this), opts));
} else if ($(this).is('.cloud-zoom-gallery')) {
opts = $.extend({}, relOpts, options);
$(this).data('relOpts', opts);
$(this).bind('click', $(this), function (event) {
var data = event.data.data('relOpts');
// Destroy the previous zoom
$('#' + data.useZoom).data('zoom').destroy();
// Change the biglink to point to the new big image.
$('#' + data.useZoom).attr('href', event.data.attr('href'));
// Change the small image to point to the new small image.
$('#' + data.useZoom + ' img').attr('src', event.data.data('relOpts').smallImage);
// Init a new zoom with the new images.
$('#zoom-cb').attr('href', event.data.attr('href'));
$('#' + event.data.data('relOpts').useZoom).CloudZoom();
return false;
});
}
});
return this;
};
$.fn.CloudZoom.defaults = {
zoomWidth: 'auto',
zoomHeight: 'auto',
position: 'right',
tint: false,
tintOpacity: 0.5,
lensOpacity: 0.5,
softFocus: false,
smoothMove: 3,
showTitle: false,
titleOpacity: 0.5,
adjustX: 0,
adjustY: 0
};
})(jQuery);
We're working with the HTML5 canvas, displaying lots of images at one time.
This is working pretty well but recently we've had a problem with chrome.
When drawing images on to a canvas you seem to reach a certain point where the performance degrades very quickly.
It's not a slow effect, it seems that you go right from 60fps to 2-4fps.
Here's some reproduction code:
// Helpers
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })();
// https://github.com/mrdoob/stats.js
var Stats = function () { var e = Date.now(), t = e; var n = 0, r = Infinity, i = 0; var s = 0, o = Infinity, u = 0; var a = 0, f = 0; var l = document.createElement("div"); l.id = "stats"; l.addEventListener("mousedown", function (e) { e.preventDefault(); y(++f % 2) }, false); l.style.cssText = "width:80px;opacity:0.9;cursor:pointer"; var c = document.createElement("div"); c.id = "fps"; c.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#002"; l.appendChild(c); var h = document.createElement("div"); h.id = "fpsText"; h.style.cssText = "color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; h.innerHTML = "FPS"; c.appendChild(h); var p = document.createElement("div"); p.id = "fpsGraph"; p.style.cssText = "position:relative;width:74px;height:30px;background-color:#0ff"; c.appendChild(p); while (p.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#113"; p.appendChild(d) } var v = document.createElement("div"); v.id = "ms"; v.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#020;display:none"; l.appendChild(v); var m = document.createElement("div"); m.id = "msText"; m.style.cssText = "color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; m.innerHTML = "MS"; v.appendChild(m); var g = document.createElement("div"); g.id = "msGraph"; g.style.cssText = "position:relative;width:74px;height:30px;background-color:#0f0"; v.appendChild(g); while (g.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#131"; g.appendChild(d) } var y = function (e) { f = e; switch (f) { case 0: c.style.display = "block"; v.style.display = "none"; break; case 1: c.style.display = "none"; v.style.display = "block"; break } }; var b = function (e, t) { var n = e.appendChild(e.firstChild); n.style.height = t + "px" }; return { REVISION: 11, domElement: l, setMode: y, begin: function () { e = Date.now() }, end: function () { var f = Date.now(); n = f - e; r = Math.min(r, n); i = Math.max(i, n); m.textContent = n + " MS (" + r + "-" + i + ")"; b(g, Math.min(30, 30 - n / 200 * 30)); a++; if (f > t + 1e3) { s = Math.round(a * 1e3 / (f - t)); o = Math.min(o, s); u = Math.max(u, s); h.textContent = s + " FPS (" + o + "-" + u + ")"; b(p, Math.min(30, 30 - s / 100 * 30)); t = f; a = 0 } return f }, update: function () { e = this.end() } } }
// Firefox events suck
function getOffsetXY(eventArgs) { return { X: eventArgs.offsetX == undefined ? eventArgs.layerX : eventArgs.offsetX, Y: eventArgs.offsetY == undefined ? eventArgs.layerY : eventArgs.offsetY }; }
function getWheelDelta(eventArgs) { if (!eventArgs) eventArgs = event; var w = eventArgs.wheelDelta; var d = eventArgs.detail; if (d) { if (w) { return w / d / 40 * d > 0 ? 1 : -1; } else { return -d / 3; } } else { return w / 120; } }
// Reproduction Code
var stats = new Stats();
document.body.appendChild(stats.domElement);
var masterCanvas = document.getElementById('canvas');
var masterContext = masterCanvas.getContext('2d');
var viewOffsetX = 0;
var viewOffsetY = 0;
var viewScaleFactor = 1;
var viewMinScaleFactor = 0.1;
var viewMaxScaleFactor = 10;
var mouseWheelSensitivity = 10; //Fudge Factor
var isMouseDown = false;
var lastMouseCoords = null;
var imageDimensionPixelCount = 25;
var paddingPixelCount = 2;
var canvasDimensionImageCount = 50;
var totalImageCount = Math.pow(canvasDimensionImageCount, 2);
var images = null;
function init() {
images = createLocalImages(totalImageCount, imageDimensionPixelCount);
initInteraction();
renderLoop();
}
function initInteraction() {
var handleMouseDown = function (eventArgs) {
isMouseDown = true;
var offsetXY = getOffsetXY(eventArgs);
lastMouseCoords = [
offsetXY.X,
offsetXY.Y
];
};
var handleMouseUp = function (eventArgs) {
isMouseDown = false;
lastMouseCoords = null;
}
var handleMouseMove = function (eventArgs) {
if (isMouseDown) {
var offsetXY = getOffsetXY(eventArgs);
var panX = offsetXY.X - lastMouseCoords[0];
var panY = offsetXY.Y - lastMouseCoords[1];
pan(panX, panY);
lastMouseCoords = [
offsetXY.X,
offsetXY.Y
];
}
};
var handleMouseWheel = function (eventArgs) {
var mouseX = eventArgs.pageX - masterCanvas.offsetLeft;
var mouseY = eventArgs.pageY - masterCanvas.offsetTop;
var zoom = 1 + (getWheelDelta(eventArgs) / mouseWheelSensitivity);
zoomAboutPoint(mouseX, mouseY, zoom);
if (eventArgs.preventDefault !== undefined) {
eventArgs.preventDefault();
} else {
return false;
}
}
masterCanvas.addEventListener("mousedown", handleMouseDown, false);
masterCanvas.addEventListener("mouseup", handleMouseUp, false);
masterCanvas.addEventListener("mousemove", handleMouseMove, false);
masterCanvas.addEventListener("mousewheel", handleMouseWheel, false);
masterCanvas.addEventListener("DOMMouseScroll", handleMouseWheel, false);
}
function pan(panX, panY) {
masterContext.translate(panX / viewScaleFactor, panY / viewScaleFactor);
viewOffsetX -= panX / viewScaleFactor;
viewOffsetY -= panY / viewScaleFactor;
}
function zoomAboutPoint(zoomX, zoomY, zoomFactor) {
var newCanvasScale = viewScaleFactor * zoomFactor;
if (newCanvasScale < viewMinScaleFactor) {
zoomFactor = viewMinScaleFactor / viewScaleFactor;
} else if (newCanvasScale > viewMaxScaleFactor) {
zoomFactor = viewMaxScaleFactor / viewScaleFactor;
}
masterContext.translate(viewOffsetX, viewOffsetY);
masterContext.scale(zoomFactor, zoomFactor);
viewOffsetX = ((zoomX / viewScaleFactor) + viewOffsetX) - (zoomX / (viewScaleFactor * zoomFactor));
viewOffsetY = ((zoomY / viewScaleFactor) + viewOffsetY) - (zoomY / (viewScaleFactor * zoomFactor));
viewScaleFactor *= zoomFactor;
masterContext.translate(-viewOffsetX, -viewOffsetY);
}
function renderLoop() {
clearCanvas();
renderCanvas();
stats.update();
requestAnimFrame(renderLoop);
}
function clearCanvas() {
masterContext.clearRect(viewOffsetX, viewOffsetY, masterCanvas.width / viewScaleFactor, masterCanvas.height / viewScaleFactor);
}
function renderCanvas() {
for (var imageY = 0; imageY < canvasDimensionImageCount; imageY++) {
for (var imageX = 0; imageX < canvasDimensionImageCount; imageX++) {
var x = imageX * (imageDimensionPixelCount + paddingPixelCount);
var y = imageY * (imageDimensionPixelCount + paddingPixelCount);
var imageIndex = (imageY * canvasDimensionImageCount) + imageX;
var image = images[imageIndex];
masterContext.drawImage(image, x, y, imageDimensionPixelCount, imageDimensionPixelCount);
}
}
}
function createLocalImages(imageCount, imageDimension) {
var tempCanvas = document.createElement('canvas');
tempCanvas.width = imageDimension;
tempCanvas.height = imageDimension;
var tempContext = tempCanvas.getContext('2d');
var images = new Array();
for (var imageIndex = 0; imageIndex < imageCount; imageIndex++) {
tempContext.clearRect(0, 0, imageDimension, imageDimension);
tempContext.fillStyle = "rgb(" + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ")";
tempContext.fillRect(0, 0, imageDimension, imageDimension);
var image = new Image();
image.src = tempCanvas.toDataURL('image/png');
images.push(image);
}
return images;
}
// Get this party started
init();
And a jsfiddle link for your interactive pleasure:
http://jsfiddle.net/BtyL6/14/
This is drawing 50px x 50px images in a 50 x 50 (2500) grid on the canvas. I've also quickly tried with 25px x 25px and 50 x 50 (2500) images.
We have other local examples that deal with bigger images and larger numbers of images and the other browser start to struggle with these at higher values.
As a quick test I jacked up the code in the js fiddle to 100px x 100px and 100 x 100 (10000) images and that was still running at 16fps when fully zoomed out. (Note: I had to lower the viewMinScaleFactor to 0.01 to fit it all in when zoomed out.)
Chrome on the other hand seems to hit some kind of limit and the FPS drops from 60 to 2-4.
Here's some info about what we've tried and the results:
We've tried using setinterval rather than requestAnimationFrame.
If you load 10 images and draw them 250 times each rather than 2500 images drawn once each then the problem goes away. This seems to indicate that chrome is hitting some kind of limit/trigger as to how much data it's storing about the rendering.
We have culling (not rendering images outside of the visual range) in our more complex examples and while this helps it's not a solution as we need to be able to show all the images at once.
We have the images only being rendered if there have been changes in our local code, against this helps (when nothing changes, obviously) but it isn't a full solution because the canvas should be interactive.
In the example code we're creating the images using a canvas, but the code can also be run hitting a web service to provide the images and the same behaviour (slowness) will be seen.
We've found it very hard to even search for this issue, most results are from a couple of years ago and woefully out of date.
If any more information would be useful then please ask!
EDIT: Changed js fiddle URL to reflect the same code as in the question. The code itself didn't actually change, just the formatting. But I want to be consistent.
EDIT: Updated jsfiddle and and code with css to prevent selection and call requestAnim after the render loop is done.
In Canary this code freezes it on my computer. As to why this happens in Chrome the simple answer is that it uses a different implementation than f.ex. FF. In-depth detail I don't know, but there is obviously room for optimizing the implementation in this area.
I can give some tip however on how you can optimize the given code to make it run in Chrome as well :-)
There are several things here:
You are storing each block of colors as images. This seem to have a huge performance impact on Canary / Chrome.
You are calling requestAnimationFrame at the beginning of the loop
You are clearing and rendering even if there are no changes
Try to (addressing the points):
If you only need solid blocks of colors, draw them directly using fillRect() instead and keep the color indexes in an array (instead of images). Even if you draw them to an off-screen canvas you will only have to do one draw to main canvas instead of multiple image draw operations.
Move requestAnimationFrame to the end of the code block to avoid stacking.
Use dirty flag to prevent unnecessary rendering:
I modified the code a bit - I modified it to use solid colors to demonstrate where the performance impact is in Chrome / Canary.
I set a dirty flag in global scope as true (to render the initial scene) which is set to true each time the mouse move occur:
//global
var isDirty = true;
//mouse move handler
var handleMouseMove = function (eventArgs) {
// other code
isDirty = true;
// other code
};
//render loop
function renderLoop() {
if (isDirty) {
clearCanvas();
renderCanvas();
}
stats.update();
requestAnimFrame(renderLoop);
}
//in renderCanvas at the end:
function renderCanvas() {
// other code
isDirty = false;
}
You will of course need to check for caveats for the isDirty flag elsewhere and also introduce more criteria if it's cleared at the wrong moment. I would store the old position of the mouse and only (in the mouse move) if it changed set the dirty flag - I didn't modify this part though.
As you can see you will be able to run this in Chrome and in FF at a higher FPS.
I also assume (I didn't test) that you can optimize the clearCanvas() function by only drawing the padding/gaps instead of clearing the whole canvas. But that need to be tested.
Added a CSS-rule to prevent the canvas to be selected when using the mouse:
For further optimizing in cases such as this, which is event driven, you don't actually need an animation loop at all. You can just call the redraw when the coords or mouse-wheel changes.
Modification:
http://jsfiddle.net/BtyL6/10/
This was a legitimate bug in chrome.
https://code.google.com/p/chromium/issues/detail?id=247912
It has now been fixed and should be in a chrome mainline release soon.
I am looking for some method to prompt the width and height of the image being uploaded using an asp file upload control. Following is the code I am using to prompt message for file size. I just want to add the width and height of the image being uploaded using that upload control. Please take a note that I can't use javascript DOM here like img.clientWidth as there is no such html object to call it.
Here's the JS Code
$(document).ready(function () {
$("#<%= fupNewImage.ClientID %>").bind("change", function () {
var fSize = ((this.files[0].size) / 1024).toFixed(2);
if (fSize <= 200) {
$("#fupSize").html('Good file size: ' + fSize + " kb Width=? and Height=?" ); // ? denotes the actual value to be put.
}
//---------More cases
});
});
Solved!!
Thanks to #Esailija
After a lot of searching I finally found a workout # REF https://stackoverflow.com/a/8904008/1584140
var _URL = window.URL || window.webkitURL;
$("#<%= fupNewImage.ClientID %>").bind("change", function () {
//================Changed Code===================
var file, img;
if ((file = this.files[0])) {
img = new Image();
img.onload = function () {
var fSize = ((file.size) / 1024).toFixed(2);
var WH = " Widht: " + this.width + "px & Height: " + this.height + "px";
if (fSize <= 200) {
$("#fupSize").html('Good file size: ' + fSize + " kb" + WH);
}
};
img.onerror = function () {
alert("not a valid file: " + file.type);
};
img.src = _URL.createObjectURL(file);
}
//====================================
});
});
Is it possible to use JavaScript to get the actual size (width and height in pixels) of a CSS referenced background image?
Yes, and I'd do it like this...
window.onload = function () {
var imageSrc = document
.getElementById('hello')
.style.backgroundImage.replace(/url\((['"])?(.*?)\1\)/gi, '$2')
.split(',')[0];
// I just broke it up on newlines for readability
var image = new Image();
image.src = imageSrc;
image.onload = function () {
var width = image.width,
height = image.height;
alert('width =' + width + ', height = ' + height);
};
};
Some notes...
We need to remove the url() part that JavaScript returns to get the proper image source. We need to split on , in case the element has multiple background images.
We make a new Image object and set its src to the new image.
We can then read the width & height.
jQuery would probably a lot less of a headache to get going.
Can't comment under answers, so here is jQuery version including background-size (posted because this question is first one in google search and may be useful to someone else than me):
function getBackgroundSize(selector, callback) {
var img = new Image(),
// here we will place image's width and height
width, height,
// here we get the size of the background and split it to array
backgroundSize = $(selector).css('background-size').split(' ');
// checking if width was set to pixel value
if (/px/.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
// checking if width was set to percent value
if (/%/.test(backgroundSize[0])) width = $(selector).parent().width() * (parseInt(backgroundSize[0]) / 100);
// checking if height was set to pixel value
if (/px/.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
// checking if height was set to percent value
if (/%/.test(backgroundSize[1])) height = $(selector).parent().height() * (parseInt(backgroundSize[0]) / 100);
img.onload = function () {
// check if width was set earlier, if not then set it now
if (typeof width == 'undefined') width = this.width;
// do the same with height
if (typeof height == 'undefined') height = this.height;
// call the callback
callback({ width: width, height: height });
}
// extract image source from css using one, simple regex
// src should be set AFTER onload handler
img.src = $(selector).css('background-image').replace(/url\(['"]*(.*?)['"]*\)/g, '$1');
}
or as jQuery plugin:
(function ($) {
// for better performance, define regexes once, before the code
var pxRegex = /px/, percentRegex = /%/, urlRegex = /url\(['"]*(.*?)['"]*\)/g;
$.fn.getBackgroundSize = function (callback) {
var img = new Image(), width, height, backgroundSize = this.css('background-size').split(' ');
if (pxRegex.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
if (percentRegex.test(backgroundSize[0])) width = this.parent().width() * (parseInt(backgroundSize[0]) / 100);
if (pxRegex.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
if (percentRegex.test(backgroundSize[1])) height = this.parent().height() * (parseInt(backgroundSize[0]) / 100);
// additional performance boost, if width and height was set just call the callback and return
if ((typeof width != 'undefined') && (typeof height != 'undefined')) {
callback({ width: width, height: height });
return this;
}
img.onload = function () {
if (typeof width == 'undefined') width = this.width;
if (typeof height == 'undefined') height = this.height;
callback({ width: width, height: height });
}
img.src = this.css('background-image').replace(urlRegex, '$1');
return this;
}
})(jQuery);
var actualImage = new Image();
actualImage.src = $('YOUR SELECTOR HERE').css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, "");
actualImage.onload = function() {
width = this.width;
height = this.height;
}
var dimension, image;
image = new Image();
image.src = {url/data}
image.onload = function() {
dimension = {
width: image.naturalWidth,
height: image.naturalHeight
};
console.log(dimension); // Actual image dimension
};
Here it is in jQuery:
var actualImage = new Image();
actualImage.src = $('YOUR SELECTOR HERE').css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, "");
actualImage.width // The actual image width
actualImage.height // The actual image height
Thanks for the sweet regex alex.
If you're using React you can create a custom hook:
import { useEffect, useState, useCallback, useRef } from 'react'
const urlRgx = /url\((['"])?(.+?)\1\)/
const getImagePromise = src =>
new Promise(resolve => {
const img = new Image()
img.onload = () =>
resolve({
src,
width: img.naturalWidth,
height: img.naturalHeight
})
img.src = src
})
const useBackgroundImageSize = (asCallbackFlagOrUrls = false) => {
const ref = useRef()
const [images, setImages] = useState(null)
const callback = useCallback(async () => {
if (Array.isArray(asCallbackFlagOrUrls)) {
const imgPromises = asCallbackFlagOrUrls.map(getImagePromise)
const imgs = await Promise.all(imgPromises)
if (ref?.current) {
setImages(imgs)
}
}
if (typeof asCallbackFlagOrUrls === 'string') {
const image = await getImagePromise(asCallbackFlagOrUrls)
if (ref?.current) {
setImages(image)
}
}
if (typeof asCallbackFlagOrUrls === 'boolean') {
if (ref.current) {
const matches = window
.getComputedStyle(ref.current)
.backgroundImage.match(new RegExp(urlRgx, 'g'))
if (Array.isArray(matches)) {
const imgPromises = matches.map(match =>
getImagePromise(match.replace(new RegExp(urlRgx), '$2'))
)
const imgs = await Promise.all(imgPromises)
if (ref?.current) {
setImages(imgs.length > 1 ? imgs : imgs[0])
}
}
}
}
}, [ref, asCallbackFlagOrUrls])
useEffect(() => {
if (asCallbackFlagOrUrls !== true) {
callback()
}
}, [asCallbackFlagOrUrls, callback])
return asCallbackFlagOrUrls === true ? [ref, images, callback] : [ref, images]
}
export { useBackgroundImageSize }
Then use it like:
const App = () => {
const [ref, image] = useBackgroundImageSize()
console.log(image) // { width, height, src }
return <div ref={ref} image={image} />
}
You can also install background-image-size-hook and use it as a dependency. See the README for more usage details.
Here is a fixed version of the code from klh's post.
I pointed out some small mistakes on the comment section of his post and was told please edit it.
And so I did.
However, reviewers Jan Wilamowski and Dave rejected it.
"This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer."
Apparently they did not see the comments section.
I had no choice but to write the revised code as a new answer.
function getBackgroundSize(selector, callback) {
var img = new Image(),
// here we will place image's width and height
width, height,
// here we get the size of the background and split it to array
backgroundSize = $(selector).css('background-size').split(' ');
// checking if width was set to pixel value
if (/px/.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
// checking if width was set to percent value
if (/%/.test(backgroundSize[0])) width = $(selector).width() * (parseInt(backgroundSize[0]) / 100);
// checking if height was set to pixel value
if (/px/.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
// checking if height was set to percent value
if (/%/.test(backgroundSize[1])) height = $(selector).height() * (parseInt(backgroundSize[1]) / 100);
img.onload = function () {
// check if width was set earlier, if not then set it now
if (typeof width == 'undefined') width = this.width;
// do the same with height
if (typeof height == 'undefined') height = this.height;
// call the callback
callback({ width: width, height: height });
}
// extract image source from css using one, simple regex
// src should be set AFTER onload handler
img.src = $(selector).css('background-image').replace(/url\(['"]*(.*?)['"]*\)/g, '$1');
}
JQuery
(function ($) {
// for better performance, define regexes once, before the code
var pxRegex = /px/, percentRegex = /%/, urlRegex = /url\(['"]*(.*?)['"]*\)/g;
$.fn.getBackgroundSize = function (callback) {
var img = new Image(), width, height, backgroundSize = this.css('background-size').split(' ');
if (pxRegex.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
if (percentRegex.test(backgroundSize[0])) width = this.width() * (parseInt(backgroundSize[0]) / 100);
if (pxRegex.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
if (percentRegex.test(backgroundSize[1])) height = this.height() * (parseInt(backgroundSize[1]) / 100);
// additional performance boost, if width and height was set just call the callback and return
if ((typeof width != 'undefined') && (typeof height != 'undefined')) {
callback({ width: width, height: height });
return this;
}
img.onload = function () {
if (typeof width == 'undefined') width = this.width;
if (typeof height == 'undefined') height = this.height;
callback({ width: width, height: height });
}
img.src = this.css('background-image').replace(urlRegex, '$1');
return this;
}
})(jQuery);