Is it possible to clone an image after load in other parts of the page without loading? - javascript

I'm faced with a simple problem. Let says my user load around 150 images through a simple <img data-image='1' src="myimg1.jpg"> <img data-image=2' src="myimg2.jpg"> .. etc
When the user hovers over one of he images.I wish to display this myimg-thisimage.jpg in a small menu at the bottom of the screen. As of now, I'm changing the src attribute in my menu as:
$('#info-poster').attr("src","myimage-thisimage.jpg");
Note: myimage-thisimage.jpg is the current hovered over image.
But, when I do this. The browser is reloading the image (because, there is a small delay). Is there any way to bypass this loading since the user has already loaded the image using a clever way of cloning a DOM element maybe?
PS: The browser image cache is enabled. Therefore, the cache isnt the problem.
Edit: I know one way is to create 300 image elements and hide the other 150 of them. But in a scenario (definitely possible) where there are close to 500 images I would have to create around 1000 DOM elements which would be a big performance issue.

You can use a canvas element to show the thumbnail, this way the image is copied and scaled locally. In the following snippet I added two canvas, in the first one the image is scaled while keeping the aspect ratio (I use the Letterboxing and Pillarboxing techniques when required); in the second one the image is stretched. I also added another image at the bottom which is ignored, as it doesn't have the data-image attribute.
Is important not to use the scaling algorithm of drawImage as it produces unsmooth results when you reduce the image a lot. To achieve this, set the logical size of the canvas to match the natural size of the image. Then copy the image to the canvas by calling the drawImage method. Finally set the display size of the canvas to the desired one. This way the browser uses a better algorithm to scale the image.
Here are some outstanding quotes from the specification of the drawImage() method:
You can be sure the image will not be reloaded, and that you have to use the natural size of the image to avoid scaling with drawImage:
If the original image data is a bitmap image, the value painted at a point in the destination rectangle is computed by filtering the original image data.
The browser decides which scaling algorithm to use. At the moment of writing this: Edge, Chrome and Firefox don't use nothing better than the bilinear or nearest-neighbor algorithms. This may change in the future:
The user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor).
function initCanvas(id,image,naturalWidth,naturalHeight){
var canvas = document.getElementById(id);
var ctx = canvas.getContext("2d");
// Set the logical size of the canvas to match the
// natural size of the image, this way we don't use
// the scaling algorithm of drawImage (It isn't good
// for reducing big images as it produces unsmooth results).
$(canvas).attr("width",naturalWidth) ;
$(canvas).attr("height",naturalHeight) ;
// Copy the image:
ctx.drawImage(image,0,0,naturalWidth,naturalHeight);
return canvas ;
}
function clearCanvas(id){
var canvas = document.getElementById(id);
var ctx = canvas.getContext("2d");
ctx.clearRect(0,0,canvas.width,canvas.height);
}
$(window).on("load", function( ){
var images = $("img").filter(function(){
var dataImage = $(this).data("image") ;
if( typeof dataImage != "number" ) return false ;
var number = parseInt(dataImage,10) ;
return number > 0 && dataImage === number ;
}) ;
images.on("mouseenter", function( ){
var naturalWidth = $(this).prop("naturalWidth") ;
var naturalHeight = $(this).prop("naturalHeight") ;
// Scaled thumbnail:
// Copy the image to canvas-scaled and get a reference to it:
var scaledCanvas = initCanvas("canvas-scaled",this,naturalWidth,naturalHeight);
// Calculate the display size of the canvas:
var hwfactor = naturalHeight/naturalWidth ;
var whfactor = naturalWidth/naturalHeight ;
var scaledWidth, scaledHeight ;
if( hwfactor >= 1 ){ // Pillarboxing
scaledHeight = "100px" ;
scaledWidth = (100*whfactor)+"px" ;
}
else{ // Letterboxing
scaledWidth = "100px" ;
scaledHeight = (100*hwfactor)+"px" ;
}
// Now we change the display size of the canvas.
// A better scaling algorithm will be used.
$(scaledCanvas).css("width",scaledWidth);
$(scaledCanvas).css("height",scaledHeight);
// Stretched thumbnail:
// Copy the image to canvas-stretched. The display size
// of canvas-stretched is already set in the style section.
initCanvas("canvas-stretched",this,naturalWidth,naturalHeight);
});
images.on("mouseleave", function( ){
clearCanvas("canvas-scaled");
clearCanvas("canvas-stretched");
});
});
body{
background: #000;
}
.wrapper img{
width: 100px ;
height: auto ;
}
#banner{
display: block ;
width: 100% ;
height: 40px ;
padding-top: 1pt ;
}
#canvas-stretched{
width: 100px ;
height: 100px ;
}
.canvas-wrapper{
display: -webkit-inline-flex ;
display: inline-flex ;
-webkit-justify-content: space-around ;
justify-content: space-around ;
-webkit-align-items: center ;
align-items: center ;
vertical-align: bottom ;
border: 1px solid #888 ;
width: 100px ;
height: 100px ;
overflow: hidden ;
}
.viewer{
display: inline-block ;
}
.viewer span{
color: #ddd ;
font-family: sans-serif ;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<span class="wrapper">
<img data-image="1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/550px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg"/>
<img data-image="2" src="https://upload.wikimedia.org/wikipedia/en/8/81/Megadrive_another_world.png"/>
<img data-image="3" src="https://upload.wikimedia.org/wikipedia/en/e/ee/TheKlingonHamlet.jpg"/>
</span>
<span class="viewer">
<span>scaled</span><br>
<div class="canvas-wrapper">
<canvas id="canvas-scaled"></canvas>
</div>
</span>
<span class="viewer">
<span>stretched</span><br>
<div class="canvas-wrapper">
<canvas id="canvas-stretched"></canvas>
</div>
</span>
<img id="banner" src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/The_southern_plane_of_the_Milky_Way_from_the_ATLASGAL_survey.jpg/320px-The_southern_plane_of_the_Milky_Way_from_the_ATLASGAL_survey.jpg"/>

This line is the problem:
$('#info-poster').attr("src","myimage-thisimage.jpg");
The browser is reloading the image because you reasign(bad practice) the "src" attribute.
Instead, you can use CSS options to display/hide "myimage-thisimage.jpg".
Since you use jQuery, we can make use of the methods: hide/show.
You mentioned "clone", I don't think you mean HTML elements clonning.
Example: (live on JS Bin)
<img id="dummy" width="200" height="150" data-image='1' src="http://europunkt.ro/wp-content/uploads/2016/06/romania.jpg">
<!-- Hidden by default -->
<img style="display:none" id="info-poster" width="200" height="150">
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<script>
var $dummy = $("#dummy");
var $infoPoster = $("#info-poster");
var infoPosterHasLoaded = false;
$dummy.on("mouseenter", function() {
// add the src attribute ONLY once
if(infoPosterHasLoaded === false){
$infoPoster.attr("src", "http://www.ilovemaramures.com/wp-content/uploads/2012/05/Pasul-Prislop.jpg")
infoPosterHasLoaded = true;
}
$infoPoster.show();
});
$dummy.on("mouseleave", function() {
$infoPoster.hide();
});
</script>
For a more fancy "hide/show" you can check jQuery Effects.
Edit - after I read your comment
In case you want to use the "data-image" attribute from the hovered element, check these objects: event.target, event.currentTarget, this
New JS Bin version.

I believe that what you are wanting is possible with the jQuery .clone() and .append() function. See the example below.
jQuery.clone()
jQuery.append()
$(function() {
$('img#ToClone').click(function(){
var imageClone = $('#ToClone').clone();
var cloneDestination = $('#CloneTo');
cloneDestination.append(imageClone);
});
});
div
{
padding:2px;
background:blue;
}
div#CloneTo
{
background:green;
}
img{
height:50px;
width:50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<p>Click on the red square to clone it below.</p>
<img id="ToClone" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGlSURBVHhe7dCxTQQAEMRACiGk/86o4UmMxEpY/KW8R5ddtH575GnFOijWQbEOinVQrINiHRTroFgHxTr4Pdbbq2K/KNZgvyjWYL8o1mC/KNZgv3gq1uf7x389Fn5jvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgv3gq1utgvyjWYL8o1mC/KNZgvyjWYL/4452finVQrINiHRTroFgHxToo1kGxDor1tMfjC8KHtIEDA9XCAAAAAElFTkSuQmCC" />
</div>
<div id="CloneTo">
<p>Clone should appear here.</p>
<!-- The cloned image should appear here. -->
</div>

You may convert the image requests at server to respond with base64 string which you can store in your own cache.
Sample code below:
HTML
<img id="image1Id" src="" />
<input type="button" onclick='javascript:loadSomeThing("image1", "", "image1Id");' value="Load Image1" />
Script
var imageArray = [];
function loadSomeThing(key, someUrl, elementId) {
var imageData = imageArray[key];
if (!imageData) {
imageData = ajaxGetImageData(someUrl);
imageArray[key] = imageData;
}
document.getElementById(elementId).src = imageData;
}
function ajaxGetImageData(url) {
//Code to get base64 image string
return "data:image/jpeg;base64,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";
}
Demo
jsFiddle

You should let the browser to do the cache handling.
I suggest you could have a <img id="info-poster" src="myimage-thisimage.jpg" class="hide-on-load"/>, so then if your browser want to load a new copy of the image, it would do it before the user mouse over your other images. (if it is a small/acceptable image that user may have to download it every page load)
Then you could simply bind $("img.bind-event").on("mouseenter", function() { $("#info-poster").show(); }); and $("img.bind-event").on("mouseleave", function() { $("#info-poster").hide(); });

IDEA
initial markup
<img data-src='myimg1.jpg' data-image='1' src='placeholder.jpg'>
after myimg1.jpg has loaded dynamically (*)
<img data-image='1' src='blob:asdfasdfasdfasdfadfa'>
Then on 'mouseenter'
infoPosterEl.src = thisImageEl.src
// update image src to an object url(e.g. "blob:") will not bother http comm.
(*)
// Fetch acutal image as blob
// Create object url for the blob
// Update this <img> src to the object url

You can store the path to each image in an array, iterate array using Array.prototype.forEach(), set the background of each <img> element using url("/path/to/image"); at mouseover of each <img> set background-size of menu element to 100% 100% at index of hovered <img> element within collection using Array.prototype.slice(), Array.prototype.splice(). The approach should request each image from server at most once, toggling the image displayed at menu element to correspond to hovered image.
var urls = ["http://placehold.it/100x100?text=1"
, "http://placehold.it/100x100?text=2"
, "http://placehold.it/100x100?text=3"
, "http://placehold.it/100x100?text=4"
, "http://placehold.it/100x100?text=5"]
, sources = []
, sizes = []
, imgs = document.querySelectorAll(".img")
, menu = document.querySelector(".menu");
function toggleImage(index) {
this.onmouseover = function() {
var curr = sizes.slice(0);
curr.splice(index, 1, "100% 100%");
menu.style.backgroundSize = curr.join(",");
}
}
urls.forEach(function(path, index) {
sources.push("url(" + path + ")");
sizes.push("0% 0%");
imgs[index].style.background = sources[index];
toggleImage.call(imgs[index], index);
});
menu.style.background = sources.join(",");
menu.style.backgroundSize = sizes.join(",");
.menu {
left: calc(100vw / 2 - 50px);
position: relative;
width: 75px;
height: 75px;
display: block;
}
<img class="img" width="100" height="100" src="" /><img class="img" width="100" height="100" /><img class="img" width="100" height="100" /><img class="img" width="100" height="100" /><img class="img" width="100" height="100" />
<div class="menu">
</div>

Related

Change CSS of element within elements and classes

I am trying to set the height of an image to be 50% of the width, but the image scales with the page. The current CSS I am targeting looks like this:
.img-quiz p span img { width: 100%; }
Here's what I've tried in js, but isn't working:
var imgQuiz = document.getElementByClassName('img-quiz').getElementsByTagName('img');
var elementStyle = window.getComputedStyle(imgQuiz);
var pixHeight = elementStyle.getPropertyValue('height');
imgQuiz.style.height = pixHeight * .5;
Also, I'm new to javascript, do I need to wrap this in a function (i.e. window.onload = function())?
Check out this JS Fiddle for help...
https://jsfiddle.net/Lm60v949/6/
var image = document.getElementsByClassName('img-quiz')[0].getElementsByTagName('img')[0];
// console.log( image ); // test to make sure the image was captured
/*
var image = document.getElementById('imgQuiz'); // much cleaner way to select
*/
image.height = image.width / 2;
// console.log( image.height, image.width ); // check the results
.img-quiz p span img { width: 100%; }
<div class="img-quiz">
<p>
<span>
<img id="imgQuiz" src="https://image.shutterstock.com/z/stock-photo--week-old-cocker-spaniel-puppy-630877553.jpg">
</span>
</p>
</div>
A couple of hiccups here:
.getElementsByClassName and .getElementsByTagName both return a HTMLCollection -- to use these, you will have to select the index of the collection that matches your image ([0], [1], [2], ... )
If you can, an ID on the targeted image would be ideal. Just easier to write and read code.
var image = document.getElementsByClassName('img-quiz')[0].getElementsByTagName('img')[0];
If you want brownie points, you can do something like:
image.onload = function(){
image.height = image.width / 2;
};
(assuming you have the image already captured and saved to the variable "image")
This is a nice sanity check to make sure the image exists (and has a height & a width) before trying to manipulate it.
You may be overthinking the problem a bit -- once an image is loaded, you can just look up the width and set the height property of the image.
There's a couple of extras that you can do here:
Check to make sure that the image width is not 0 (zero) ... this is a nice double-check that the image has loaded
if( image.width > 0 ){ ... }
Round the number down to a whole number (there's nothing wrong with .5 size increments, just nice to work with whole numbers)
image.height = Math.floor( image.width/2 );
Summary of your question:
My understanding of your situation is, your trying to make an image have the height of 50% of the webpage but its not doing so, you have looked into different methods including JavaScript but struggling, also you asked about onload methods.
If so this is my proposed solution
Solution
In this example we are using pure JavaScript (no library or framework)
The html has pre-set the src for the image, if you want to dynamically set the src then use the following imgEl.src = "path/to/file.jpg";
The method or trigger used is a DOMContentLoaded, which ensures all the HTML elements of the page have loaded before it begins trying to read and manipulate the DOM Elements.
The function we trigger is called funStart (Short for function Start), I always encourage people to use abreviations that define the object type so that it is easier to read such as fun for function, str for string, bl for boolean, obj for object, int for integer so on.
inside funStart we are assigning an DOM element as imgEl which is an image obj and we are saying set the width to be innerWidth which is the document width
We are then saying set the height to be 50% of the document height (innerHeight), by dividing the value into 2.
function funStart(){
var imgEl = document.getElementById("targetImg");
imgEl.height = innerHeight / 2;
imgEl.width = innerWidth;
}
document.addEventListener("DOMContentLoaded", funStart, false);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<img id="targetImg" src="https://static.pexels.com/photos/257360/pexels-photo-257360.jpeg" alt="background image" title="background">
</body>
</html>
Here's a simple example (you can learn more in here):
var image = document.getElementsByClassName('img-quiz')[0];
image.height = image.width / 2;
<img src="https://dummyimage.com/100x100">
<img class="img-quiz" src="https://dummyimage.com/100x100">
The HTML file would be like this:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="https://dummyimage.com/100x100">
<img class="img-quiz" src="https://dummyimage.com/100x100">
<script>
// you could also move the script tag to the head and add an event handler to the event load (window.onload = function() {...};
var image = document.getElementsByClassName('img-quiz')[0];
image.height = image.width / 2;
</script>
</body>
</html>

Is SVG resizing this hard ? What am I missing?

I am trying to write code that resizes an SVG overlay on top of an image. The server I am working with returns both the image and via an API, a list of polygon points I need to overlay on top of the image.
This is what it should look like (the image below is a correctly aligned SVG layer). The image size is 1280x720 (I've scaled it down)
What I need to do in my app (ionic v1 app) is to make sure the SVG overlay resizes as the browser window resizes and it seems to be very hard. Here is my approach:
I am trapping a window resize event and when the image is resized, I scale the SVG polygon points relative to the size of the drawn window as it seems there is really no way to "automatically" scale the SVG by the browser like it does with an image.
Here is my code pen as you see it doesn't work as intended when I rescale (and for that matter in when its full size the overlays are not accurate). The overlays don't look accurate and when I resize it all messed up. Can someone help?
Given SO needs a code block for codepen links here it is, but its just easier to look at the codepen if you want to run it
CSS:
.imagecontainer{position:relative; margin:0 auto;}
.zonelayer
{
position:absolute;
top:0;
left:0;
background:none;
}
.zonelayer polygon {
fill-opacity: 0.25;
stroke-width: 2px;
}
.Active {
stroke: #ff0000;
fill: #ff0000;
}
HTML code:
<ion-content>
image:{{disp}}<br/>
<small>points: <span ng-repeat="item in zoneArray">{{item}}</span></small>
<div class="imagecontainer">
<img id="singlemonitor" style="width:100vw; height:100vh;object-fit:contain" ng-src="http://m9.i.pbase.com/o9/63/103963/1/164771719.2SfdldRy.nphzms.jpeg" />
<div class="zonelayer">
<svg ng-attr-width="{{cw}}" ng-attr-height="{{ch}}" class="zonelayer" ng-attr-viewBox="0 0 {{cw}} {{ch}}">
<polygon ng-repeat="item in zoneArray" ng-attr-points="{{item}}" class="Active"/> </polygon>
</svg>
</div>
</div>
</ion-content>
JS controller:
window.addEventListener('resize', liveloaded);
liveloaded();
// credit: http://stackoverflow.com/questions/41411891/most-elegant-way-to-parse-scale-and-re-string-a-string-of-number-co-ordinates?noredirect=1#41411927
function scaleCoords(string, sx, sy) {
var f = [sx, sy];
return string.split(' ').map(function (a) {
return a.split(',').map(function (b, i) {
return Math.round(b * f[i]);
}).join(',');
}).join(' ');
}
function liveloaded()
{
$timeout (function () {
console.log ("IMAGE LOADED");
var img =document.getElementById("singlemonitor");
//var offset = img.getBoundingClientRect();
$scope.cw = img.clientWidth;
$scope.ch = img.clientHeight;
$scope.vx = img.offsetWidth;
$scope.vy = img.offsetHeight;
var rect = img.getBoundingClientRect();
//console.log(rect.top, rect.right, rect.bottom, rect.left);
$scope.disp = img.clientWidth+ "x"+img.clientHeight + " with offsets:"+$scope.vx+"/"+$scope.vy;
$scope.zoneArray = [
"598,70 700,101 658,531 516,436",
"531,243 687,316 663,593 360,717 191,520",
"929,180 1108,248 985,707 847,676",
"275,17 422,45 412,312 271,235",
];
var ow = 1280;
var oh = 720;
for (var i=0; i < $scope.zoneArray.length; i++)
{
var sx = $scope.cw/ow;
var sy = $scope.ch/oh;
$scope.zoneArray[i] = scaleCoords($scope.zoneArray[i],sx,sy);
console.log ("SCALED:"+$scope.zoneArray[i]);
}
});
}
There are a couple of issues with your code.
The main problem is you can't use ng-attr-viewBox, because angular will "normalise" the attribute to lower case. It turns the attribute into viewbox (lower case B) which is (currently) invalid. viewBox is case sensitive.
The solution is to use a special trick of Angular to preserve camel-case. If you use ng-attr-view_box, it will generate the correctly camel-cased attribute name of viewBox.
<svg width="100vw" height="100vh" class="zonelayer" ng-attr-view_box="0 0 {{cw}} {{ch}}">
The other thing is that you are using the wrong width and height values for the viewBox. You need to use the natural/intrinsic image dimensions in your viewBox.
$scope.cw = img.naturalWidth;
$scope.ch = img.naturalHeight;
Link to updated code pen

Duplicating a canvas many times: clone the canvas or copy the image data?

One of my interface elements is being rendered using the HTML5 <canvas> element and associated JavaScript API. This element is used in several places on the same screen and on multiple screens throughout the app. What is the most efficient way to display this everywhere it's required?
My first idea is to draw to a master canvas, which I then clone and insert where needed in the page. The master canvas might be something like:
var master = $('<canvas>').attr({
width: 100,
height: 100
}),
c = master[0],
ctx = c.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
Let's say I want to duplicate the canvas in these div containers:
<div class="square-container" id="square_header"></div>
...
<div class="square-container" id="square_dataTable"></div>
...
<div class="square-container" id="square_gallery"></div>
....
When the page loads, I'll do this to insert a duplicate canvas element into each container:
$(document).ready(function() {
$('.square-container').each(function() {
master.clone().appendTo($(this));
});
});
The content being rendered on the canvas is going to be more complex than the simple square used in this example but will still end up being just a static image. It is possible, though, that there could be dozens of different images each cloned dozens of times per page.
The other approach I had in mind was to create an image using the toDataURL() method and set that as the appropriate images' sources:
var master = $('<canvas>').attr({
width: 100,
height: 100
}),
c = master[0],
ctx = c.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0,0,150,75);
var square = c.toDataURL('image/png');
I would add image tags where necessary:
<img src="" id="square_header" class="square" alt="" />
...
<img src="" id="square_dataTable1" class="square" alt="" />
...
<img src="" id="square_gallery" class="square" alt="" />
....
And then set all of their SRCs to that newly created image:
$(document).ready(function() {
$('img.square').attr('src', square);
});
To me, it pretty much looks like six of one, half dozen of the other. But I'm wondering if one way is considered better practice than the other? If the content being rendered on the <canvas> were more complex, would one way be more efficient than the other?
In that same spirit, when I need to use that element on subsequent pages, is it best to execute all the javascript (from whatever solution is deemed best above) on each page or would saving the value of CANVAS_ELEMENT.toDataURL() in a cookie and then using that on subsequent pages be any more efficient?
Cloning a canvas will duplicate its dimensions and styling, but not its image data. You can copy the image data by calling drawImage on the context. To paint the contents of originalCanvas onto duplicateCanvas, write:
duplicateCanvas.getContext('2d').drawImage(originalCanvas, 0, 0);
As a demonstration, the following snippet generates four canvases:
an original canvas with a small scene painted onto it
a copy made by calling cloneNode only
a copy made by calling cloneNode and drawImage
a copy made by creating a new image and setting its source to the data URI
function message(s) {
document.getElementById('message').innerHTML += s + '<br />';
}
function timeIt(action, description, initializer) {
var totalTime = 0,
initializer = initializer || function () {};
initializer();
var startTime = performance.now();
action();
var elapsed = performance.now() - startTime;
message('<span class="time"><span class="number">' +
Math.round(elapsed * 1000) + ' μs</span></span> ' + description);
}
function makeCanvas() {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;
timeIt(function () {
context.fillStyle = '#a63d3d';
context.fillRect(10, 10, 80, 40); // Paint a small scene.
context.fillStyle = '#3b618c';
context.beginPath();
context.arc(60, 60, 25, 0, 2*Math.PI);
context.closePath();
context.fill();
}, '(millionths of a second) to draw original scene', function () {
context.clearRect(0, 0, canvas.width, canvas.height);
});
return canvas;
}
// copyCanvas returns a canvas containing the same image as the given canvas.
function copyCanvas(original) {
var copy;
timeIt(function () {
copy = original.cloneNode(); // Copy the canvas dimensions.
copy.getContext('2d').drawImage(original, 0, 0); // Copy the image.
}, 'to copy canvas with cloneNode and drawImage');
return copy;
}
// imageFromStorage extracts the image data from a canvas, stores the image data
// in a browser session, then retrieves the image data from the session and
// makes a new image element out of it. We measure the total time to retrieve
// the data and make the image.
function imageFromStorage(original) {
var image,
dataURI = original.toDataURL();
timeIt(function () {
image = document.createElement('img');
image.src = dataURI;
}, 'to make image from a dataURI');
return image;
}
function pageLoad() {
var target = document.getElementById('canvases'),
containers = {}, // We'll put the canvases inside divs.
names = ['original', 'cloneNode', 'drawImage', 'dataURI'];
for (var i = 0; i < names.length; ++i) {
var name = names[i], // Use the name as an ID and a visible header.
container = document.createElement('div'),
header = document.createElement('div');
container.className = 'container';
header.className = 'header';
header.innerHTML = container.id = name;
container.appendChild(header);
target.appendChild(container);
containers[name] = container; // The canvas container is ready.
}
var canvas = makeCanvas();
containers.original.appendChild(canvas); // Original canvas.
containers.cloneNode.appendChild(canvas.cloneNode()); // cloneNode
containers.drawImage.appendChild(copyCanvas(canvas)); // cloneNode + drawImage
containers.dataURI.appendChild(imageFromStorage(canvas)); // localStorage
}
pageLoad();
body {
font-family: sans-serif;
}
.header {
font-size: 18px;
}
.container {
margin: 10px;
display: inline-block;
}
canvas, img {
border: 1px solid #eee;
}
#message {
color: #666;
font-size: 16px;
line-height: 28px;
}
#message .time {
display: inline-block;
text-align: right;
width: 100px;
}
#message .number {
font-weight: bold;
padding: 1px 3px;
color: #222;
background: #efedd4;
}
<div id="canvases"></div>
<div id="message"></div>
If you call toDataURL to copy the image data into a string for use in other pages, don't put the string into a cookie. Cookies are meant to store small amounts of data. Instead, use the HTML5 Web Storage API to store the image data in the browser. Alternatively, if the image doesn't change between user sessions, you can render it to a PNG image on a server and use the Cache-Control header to encourage the browser to cache the image file for fast retrieval.
When it comes to the performance of client-side image rendering, it may be faster to draw the scene anew than to paint the stringified image data onto the canvas. Decoding the string and painting the pixels is a relatively expensive operation. To find out if it makes sense to redraw the scene on each page, you can time your drawing operations with performance.now, as demonstrated in the snippet.

onload triggering too early for ajax content in IE

I have a page where the images are supplied dynamically and are scaled with javascript to fit within the appropriate dimensions. This was initially being done with an onload attribute in the img tag, but then I noticed that in IE, the height being returned for the image was much less in some cases than the actual height, which ended up distorting the image. I solved this by finding and resizing all the images after $(window).load() was done, which worked fine for the initial page load, but I also have the page set up to add more content with an ajax call. For the ajax content, I tried some code I found on here that improved the problem, but didn't completely solve it. Here is an example of one of my image tags
<img id="img<?php echo $prodModObj->rolloverID; ?>" class="mbImg unsized" src="<?php echo $prodModObj->img; ?>" alt="<?php echo $prodModObj->name; ?>" onerror="swapImage(<?php echo $prodModObj->rolloverID; ?>)" />
The swapImage function just swaps out the image with a placeholder if there is an error while loading. Here is my JS
function swapImage(thisImgID) {
var imgID = 'img#img' + thisImgID;
$(imgID).attr('src', '/images/NoImageAvail.jpg');
}
function checkImage(thisImgID, fitDimension, spaceDimension) {
var imgID = 'img#img' + thisImgID;
var imgHeight = $(imgID).height();
var imgWidth = $(imgID).width();
var displayHeight, displayWidth, newMargin;
if (imgHeight > imgWidth) {
displayHeight = fitDimension;
displayWidth = imgWidth*(displayHeight/imgHeight);
} else if (imgHeight < imgWidth) {
displayWidth = fitDimension;
displayHeight = imgHeight*(displayWidth/imgWidth);
} else {
displayWidth = fitDimension;
displayHeight = fitDimension;
}
$(imgID).css('height', displayHeight);
$(imgID).css('width', displayWidth);
newMargin = ((spaceDimension - displayHeight)/2);
$(imgID).css('margin-top', newMargin);
$(imgID).removeClass('mbImg unsized').addClass('mbImg sized');
}
And then on the page I have
$(window).load(function(){
// Resize product images
$('.mbImg.unsized').each( function() {
var rolloverID = $(this).attr('id').substr(3);
checkImage(rolloverID,250,270);
});
});
And then in the success portion of the ajax call, I have
$('.mbImg.unsized').each( function() {
var rolloverID = $(this).attr('id').substr(3);
if (this.complete) {
checkImage(rolloverID,250,270);
} else {
$(this).on('load', function(){
checkImage(rolloverID,250,270);
});
}
});
Images that have been cached by the browser work fine, and the images in the initial page load work fine, but about 1 in 5 of new ajax images come out distorted. Is there another method I can use to size all the ajax images correctly in IE?
Thanks for your help,
Maybe come at it another way?
I've tried to move away from html4 style tag syntax, to using simple html5 tags and a combination of JavaScript and CSS to control the "view".
Check out this fiddle:
http://jsfiddle.net/zacwolf/s1haq3mz/
A question becomes how you want your images to flow, as using this approach all of the images are technically the same size (as demonstrated by the border). Also note that the .src for the second image I tweeked the url a bit so that it was a 404 for the image file, which triggered the one error image instead.
<img id="one" class="myclass" />
<img id="two" class="myclass" />
<style>
.myclass{
height:270px;
width:250px;
background-position:center,center;
background-repeat:no-repeat;
background-size:contain;
}
</style>
<script>
var one = new Image();
one.onerror=
function(){
this.src='http://leomarketingep.com/wp-content/uploads/Sign-Error-icon.png'
}
one.onload=
function(){
$('#one').css('background-image','url('+one.src+')')
}
one.src='https://cjjulian.files.wordpress.com/2009/04/blah_blah_blah-703369.jpg';
var two = new Image();
two.onerror=
function(){
this.src='http://leomarketingep.com/wp-content/uploads/Sign-Error-icon.png';
}
two.onload=
function(){
$('#two').css('background-image','url('+two.src+')')
}
two.src='https://cjjulian.files.wordpress.com/2019/04/blah_blah_blah-703369.jpg';
</script>
If you have a lot of images, you can populate an array of Image objects, for better referencing, etc.

Cannot save canvas with draggable object

I'm trying to save my HTML canvas to file which I can successfully do, but it's not saving any objects I've dragged into the canvas.
So, by using the Draggable JQuery I can happily move my object around screen and place it ontop of my canvas. When I save the canvas using the Canvas.ToDataURL() it does not save my dragged objects (and also does something strange to my canvas in the jsFiddle, it appears to change the colour of my canvas?).
To see a "working" example, please visit my jsFiddle http://jsfiddle.net/JVSFS/74/
Please simply drag the green box over the blue box and click the save button. The result will be shown underneath (just an orange box).
HTML
<canvas id="MyCanvas" class="canvas"></canvas>
<div class="popup_click">
<div id="popup_title">Drag</div>
</div>
<asp:HiddenField ID="hideMe" runat="server" />
<asp:Button runat="server" OnClick="ClickMe" Text="Click" OnClientClick="SaveMe()" />
<button onclick="SaveMe()">Try it</button>
<p>Results: </p>
<img id="myImage" />
JavaScript
$(document).ready(function () {
$('.popup_click').show(0).draggable();
});
function SaveMe() {
var canvas = document.getElementById("MyCanvas");
var context = canvas.getContext("2d");
context.fillStyle = "orange";
context.fillRect(0, 0, 100, 100);
var image = canvas.toDataURL("image/png");
document.getElementById("myImage").src = image;
document.getElementById("hideMe").value = image;
}
CSS
.popup_click {
background: #80FF80;
width: 50px; }
.canvas {
width: 100px;
height: 100px;
background-color: #0FC;
}
How can I get the dragged object to save? I assume I have to tell the Canvas that the object is part of it's context but no idea how and my own searches came up with nothing.
From https://developer.mozilla.org/en-US/docs/HTML/Canvas/Drawing_DOM_objects_into_a_canvas
You can't just draw HTML into a canvas. Instead, you need to use an SVG image containing the content you want to render. To draw HTML content, you'd use a element containing the HTML, then draw that SVG image into your canvas.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var data = "<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'>" +
"<foreignObject width='100%' height='100%'>" +
"<div xmlns='http://www.w3.org/1999/xhtml' style='font-size:40px'>" +
"<em>I</em> like <span style='color:white; text-shadow:0 0 2px blue;'>cheese</span>" +
"</div>" +
"</foreignObject>" +
"</svg>";
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
};
img.src = url;
That's because your draggable object isn't in the canves.
They are simple html elements.
It'll save only the objects whose created with canvas methods.
Any way to create html elements on canvas you have to use svg.
Mozilla show nice way to to this but you need to get all the css to inline css before.
mozilla explanation
Anyway with using svg on you canvas you won't be able to use toDataUrl because of security policy.

Categories

Resources