i am trying to create an image canvas where user can zoom into the image, the code which i got from here enter link description here, now i tried to add image inside it and i did the following code:
function draw(scale, translatePos) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
make_base(context);
}
function make_base(context) {
var base_image = new Image();
base_image.src = 'https://www.gstatic.com/webp/gallery3/1.sm.png';
base_image.onload = function() {
context.drawImage(base_image, 0, 0);
}
}
window.onload = function() {
var canvas = document.getElementById("myCanvas");
var translatePos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var scale = 1.0;
var scaleMultiplier = 0.8;
var startDragOffset = {};
var mouseDown = false;
// add button event listeners
document.getElementById("plus").addEventListener("click", function() {
scale /= scaleMultiplier;
draw(scale, translatePos);
}, false);
document.getElementById("minus").addEventListener("click", function() {
scale *= scaleMultiplier;
draw(scale, translatePos);
}, false);
// add event listeners to handle screen drag
canvas.addEventListener("mousedown", function(evt) {
mouseDown = true;
startDragOffset.x = evt.clientX - translatePos.x;
startDragOffset.y = evt.clientY - translatePos.y;
});
canvas.addEventListener("mouseup", function(evt) {
mouseDown = false;
});
canvas.addEventListener("mouseover", function(evt) {
mouseDown = false;
});
canvas.addEventListener("mouseout", function(evt) {
mouseDown = false;
});
canvas.addEventListener("mousemove", function(evt) {
if (mouseDown) {
translatePos.x = evt.clientX - startDragOffset.x;
translatePos.y = evt.clientY - startDragOffset.y;
draw(scale, translatePos);
}
});
draw(scale, translatePos);
};
jQuery(document).ready(function() {
$("#wrapper").mouseover(function(e) {
$('#status').html(e.pageX + ', ' + e.pageY);
});
})
body {
margin: 0px;
padding: 0px;
}
#wrapper {
position: relative;
border: 1px solid #9C9898;
width: 578px;
height: 200px;
}
#buttonWrapper {
position: absolute;
width: 30px;
top: 2px;
right: 2px;
}
input[type="button"] {
padding: 5px;
width: 30px;
margin: 0px 0px 2px 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<body onmousedown="return false;">
<div id="wrapper">
<canvas id="myCanvas" width="578" height="200">
</canvas>
<div id="buttonWrapper">
<input type="button" id="plus" value="+"><input type="button" id="minus" value="-">
</div>
</div>
<h2 id="status">
0, 0
</h2>
</body>
however the image is not getting displayed inside the canvas, can anyone please tell me what could be wrong in here, thanks in advance
Your draw function never actually draws to the canvas. You get the canvas and context in the first 2 lines, but you need to call drawImage with the image to actually add it to the canvas itself.
I suspect you want to be calling make_base inside it like so:
function draw(scale, translatePos) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
make_base();
}
You also need to have the context in the same scope as you use it. At the moment, the variable context only exists inside the draw function and not the make_base function, so you can't access it from inside make_base.
You can pass it as a variable like so:
function draw(scale, translatePos) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
make_base(context);
}
function make_base(context) {
var base_image = new Image();
base_image.src = 'a2.jpg';
base_image.onload = function() {
context.drawImage(base_image, 0, 0);
}
}
Every time you want to change anything on an HTML canvas you need to call draw functions to change what's there.
I am trying to draw images on a canvas in a loop
In the following code, if I click on Next the 5 images appear one after the other. If I click on All only the last image appears
What is wrong?
<html>
<head>
<style>
.display-grid{
display: grid;
grid-template-columns: auto auto auto auto;
padding: 10px;
}
</style>
</head>
<body>
<div id="my-grid">
<div class="display-grid">
<span><canvas class="canvas" id="display-0"></canvas></span>
<span><canvas class="canvas" id="display-1"></canvas></span>
<span><canvas class="canvas" id="display-2"></canvas></span>
<span><canvas class="canvas" id="display-3"></canvas></span>
<span><canvas class="canvas" id="display-4"></canvas></span>
</div>
</div>
<button id="next-image">Next</button>
<button id="all-images">All</button>
<script type="text/javascript">
last_image = -1
var next_button= document.getElementById('next-image');
var all_button= document.getElementById('all-images');
next_button.onclick = function(){nextImage()};
all_button.onclick = function(){allImages()};
function nextImage() {
last_image ++
var canvas = document.getElementById('display-'+last_image.toString());
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0);
};
imageObj.src = 'image_'+last_image.toString()+'.png';
}
function allImages() {
for (index = 0; index < 5; index++) {
var canvas = document.getElementById('display-'+index.toString());
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0);
};
imageObj.src = 'image_'+index.toString()+'.png';
}
}
</script>
</body>
</html>
It happens because the onload function isn't called in the same order as the loop. Some images may take longer to load than others.
You could fix it by checking the image name to know which image is loaded, and then you know in which canvas it should be drawn.
function allImages() {
for (let index = 0; index < 5; index++) {
let imageObj = new Image();
imageObj.name = "display-"+index
imageObj.onload = function() {
console.log("loaded : " + this.name)
let canvas = document.getElementById(this.name);
let context = canvas.getContext('2d');
context.drawImage(this, 0, 0);
};
imageObj.src = 'image_'+index+'.png';
}
}
I can add a local image to my canvas. My problem though is that I can only scale the image by something like 0.5 but this isn't very helpful because images are always different. How might I have the image scale to say 400px wide, but the rest resize proportionally so that no matter the size of the chosen image, things fit and it isn't a guessing game (currently I have a link to an image resizer, I'm trying to remove the need)?
I'm using fabricjs 1.7.21.
var canvas = new fabric.Canvas("c");
canvas.setHeight(616);
canvas.setWidth(446);
// New Photo to Canvas
document.getElementById('imgLoader').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.set({
left: 10,
top: 10,
}).scale(0.5);
canvas.add(image);
}
}
reader.readAsDataURL(e.target.files[0]);
}
canvas {
border: 1px solid #dddddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label class="btn btn-default" id="imgLoader">
<span class="oi oi-image"></span> Add Image<input type="file" hidden>
</label>
<canvas id="c"></canvas>
Try this:
var canvas = new fabric.Canvas("c");
canvas.setHeight(616);
canvas.setWidth(446);
// New Photo to Canvas
document.getElementById('imgLoader').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.width = 400;
image.height = 400;
image.set({
left: 10,
top: 10,
});
canvas.add(image);
}
}
reader.readAsDataURL(e.target.files[0]);
}
scaleToWidth/scaleToHeight was what I was looking for. You can find the documentation here.
Props to #Ben who commented this. I wanted to close the question.
EDIT (original post at bottom below code)**
Fiddle: https://jsfiddle.net/Tron4949/oezuz1g9/9/
(fiddle example will not have a background, it's unnecessary to demonstrate the issue, the console log shows more detail about which view you are on, what was just saved, etc.)
Here is my attempt based on the suggestions from below. It has changed forms many times - currently when the user loads an image, and positions it where they want, then toggles to another view, the uploaded image carries over to that other view (which is undesired - each view should have their own specific layout).
I added 'front', 'side', and 'back' buttons to correspond to different canvas views. When you toggle back and forth - I track the canvas view ("front", "side", or "back") in session storage, defaulting to "front" at the very beginning. Depending on which button is pressed (for which view), it redefines the string in sessionStorage so that we know what the previous view was supposed to be.
It then saves the content of the canvas to storage as a dataURL according to the view it just came from. (*It then clears the canvas and accesses the dataURL for the correct position of the background picture that the user chose earlier on - this is not displayed in the fiddle).
From this point is where I keep having to re-configure things, and I can not find a solution to make it run properly, that is, to make it run without images carrying over.
As is, the image that the user uploaded not only carries over to the other views when you toggle between them, but anything I do with that image, or a newly uploaded one, continues to carry over as you toggle back and forth between views, to the point where each of the three uploaded images and positions are all the same(unexpected/undesired).
A stripped down version - The three click handlers for 'views' are basically the same:
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<canvas id="c" width="740" height="500" style="border-left: 3px solid black; border-top: 3px solid black; border-radius: 10px;"></canvas>
<button class="btn" id="customer-chosen-background-image">Set A Background Image</button>
<input class="btn" type="file" id="files" name="files[]"/>
<button class="btn" id="sessionInfoClear">Clear My Session</button>
<hr>
<button class="btn view-btn" id="front-hat-view-button">Front</button>
<button class="btn view-btn" id="side-hat-view-button">Side</button>
<button class="btn view-btn" id="back-hat-view-button">Back</button>
<canvas id="d" width="740" height="250" style="border-left: 3px solid black; border-top: 3px solid black; border-radius: 10px;"></canvas>
JS
$(document).ready(function() {
var canvas = new fabric.Canvas('c');
var canvas2 = new fabric.Canvas('d');
var scopeTesterOne = "testing scope";//SOMETHING I DO OCCASIONALLY
document.getElementById('files').addEventListener("change", function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function (f) {
var data = f.target.result;
fabric.Image.fromURL(data, function (img) {
var oImg = img.set({left: 350, top: 300, angle: 00,width:100, height:100}).scale(0.9);
canvas.add(oImg).renderAll();
var a = canvas.setActiveObject(oImg);
});
};
reader.readAsDataURL(file);
$('#files').css({'background-color': 'white', 'color': 'black', 'border': '2px solid black'});
});
$('#customer-chosen-background-image').click(function() {
var backgroundImageFilePath = 'http://store-1p9yfoynqe.mybigcommerce.com/product_images/Viewsforfiddle.png';
// var backgroundURL = "url(" + backgroundImageFilePath + ")"
canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
top: 0,
left: 50,
scaleY: .8,
scaleX: .8
});
});
var whatIsTheHatViewOnCanvas = function() {//INITIALLY THIS IS HOW WE TELL/TRACK/SET WHAT THE PREVIOUS VIEW WAS
var doesTrackingDataExist = sessionStorage.getItem('viewForHatCanvas');
if (doesTrackingDataExist) {
console.log("traker has previous view data");
} else {
console.log("traker has no previous view data");
sessionStorage.setItem('viewForHatCanvas', "front");
var consoleTestttt = sessionStorage.getItem('viewForHatCanvas');
console.log(consoleTestttt);
}
};
whatIsTheHatViewOnCanvas();
function loadCanvas(dataURL) { //THIS IS FOR THE MAIN CANVAS
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 0, 0);
};
imageObj.src = dataURL;
}
function loadPreviewCanvasFront(dataURL) {//IMAGES FOR THE 'PREVIEW' CANVAS
var canvas2 = document.getElementById('d');
var context = canvas2.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 0, 0, 185, 125);
};
imageObj.src = dataURL;
}
function loadPreviewCanvasSide(dataURL) {//IMAGES FOR THE 'PREVIEW' CANVAS
var canvas2 = document.getElementById('d');
var context = canvas2.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 185, 0, 185, 125);
};
imageObj.src = dataURL;
}
function loadPreviewCanvasBack(dataURL) {//IMAGES FOR THE 'PREVIEW' CANVAS
var canvas2 = document.getElementById('d');
var context = canvas2.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 370, 0, 185, 125);
};
imageObj.src = dataURL;
}
$('#front-hat-view-button').click(function() {
console.log(scopeTesterOne);
var context = canvas.getContext('2d');
var previousHatView = sessionStorage.getItem('viewForHatCanvas');
sessionStorage.setItem('viewForHatCanvas', "front");
console.log("from " + previousHatView + " to front");
canvas.deactivateAll().renderAll();
var savingCanvasContentF = canvas.toDataURL("image");
if (previousHatView === "side") {
sessionStorage.setItem('storedSideHatCreation', savingCanvasContentF);
console.log("from " + previousHatView + " to front - STORED AS SIDE VIEW");
}
if (previousHatView === "back") {
sessionStorage.setItem('storedBackHatCreation', savingCanvasContentF);
console.log("from " + previousHatView + " to back - STORED AS BACK VIEW");
}
// canvas.clear();
context.clearRect(0, 0, canvas.width, canvas.height);
var imgToLoadOnFrontView = sessionStorage.getItem('storedFrontHatCreation');
//var backgroundImageFilePath =
//var backgroundURL = "url(" + backgroundImageFilePath + ")"
//canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
//top: 0,
//left: 50,
//scaleY: .8,
//scaleX: .8
//});
if (imgToLoadOnFrontView != null) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("says there is an image to load on front");
loadCanvas(imgToLoadOnFrontView);
loadPreviewCanvasFront(imgToLoadOnFrontView);
}
});
$('#side-hat-view-button').click(function() {
var context = canvas.getContext('2d');
var previousHatView = sessionStorage.getItem('viewForHatCanvas');
var currentHatView = "side";
sessionStorage.setItem('viewForHatCanvas', "side");
canvas.deactivateAll().renderAll();
var savingCanvasContentS = canvas.toDataURL("image");
if (previousHatView === "front") {
sessionStorage.setItem('storedFrontHatCreation', savingCanvasContentS);
console.log("from " + previousHatView + " to side - STORED AS FRONT VIEW");
}
if (previousHatView === "back") {
sessionStorage.setItem('storedBackHatCreation', savingCanvasContentS);
console.log("from " + previousHatView + " to side - STORED AS BACK VIEW");
}
// canvas.clear();
context.clearRect(0, 0, canvas.width, canvas.height);
var imgToLoadOnSideView = sessionStorage.getItem('storedSideHatCreation');
// var backgroundImageFilePath = sessionStorage.getItem('hatImageLocation');
// var backgroundURL = "url(" + backgroundImageFilePath + ")"
// canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
// top: -560,
// left: 50,
// scaleY: .8,
// scaleX: .8
// });
if (imgToLoadOnSideView != null) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("says there is an image to load on side");
loadCanvas(imgToLoadOnSideView);
loadPreviewCanvasSide(imgToLoadOnSideView);
}
});
$('#back-hat-view-button').click(function() {
var context = canvas.getContext('2d');
var previousHatView = sessionStorage.getItem('viewForHatCanvas');
sessionStorage.setItem('viewForHatCanvas', "back");
console.log("from " + previousHatView + " to back");
canvas.deactivateAll().renderAll();
var savingCanvasContentB = canvas.toDataURL("image");
if (previousHatView === "front") {
sessionStorage.setItem('storedFrontHatCreation', savingCanvasContentB);
console.log("from " + previousHatView + " to back - STORED AS FRONT VIEW");
}
if (previousHatView === "side") {
sessionStorage.setItem('storedSideHatCreation', savingCanvasContentB);
console.log("from " + previousHatView + " to back - STORED AS SIDE VIEW");
}
// canvas.clear();
context.clearRect(0, 0, canvas.width, canvas.height);
var imgToLoadOnBackView = sessionStorage.getItem('storedBackHatCreation');
//var backgroundImageFilePath = sessionStorage.getItem('hatImageLocation');
//var backgroundURL = "url(" + backgroundImageFilePath + ")"
//canvas.setBackgroundImage(backgroundImageFilePath, canvas.renderAll.bind(canvas), {
// top: -1065,
// left: 50,
// scaleY: .8,
// scaleX: .8
//});
if (imgToLoadOnBackView != null) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("says there is an image to load on back");
loadCanvas(imgToLoadOnBackView);
loadPreviewCanvasBack(imgToLoadOnBackView);
}
});
$('#sessionInfoClear').click(function() {
sessionStorage.clear();
location.reload();
});
});
ORIGINAL POST (below):
I have a canvas that users can interact with (to create a hat). It has one background image that has three 'sections' on it for front side and back. As of now, a user is able to upload their own images and manipulate them on the canvas to make their own designs - I am using Fabric.js for image manipulation.
My Goal: Although the background image is one large image with three 'sections' on it, I only want the user to be able to see one of those sections at a time, and then be able to switch back and forth between any of the three of them while they make their picture/creation (without a page load - page load removes the uploaded images).
My thoughts: Change the canvas's viewable area on a click - I can not figure out how to make a specific viewable area but I feel like this is the best option I can think of. I just need to be able to figure out how to have a 'viewing window' that is smaller than the canvas, and then I could use thumbnails or buttons to change whatever parameters I need to change so that it appears to be a different canvas (without a reload - it will remove the images).
Is this realistic and or possible? Is there a particular/better way that I have not considered? I really would like to keep the app (mostly) in-tact, but if I absolutely have to change everything, then I will, but I'm hoping someone knows how to do what I am proposing. Thanks so much for your time.
I think there is an easier solution then what you are trying to do. if you have the reference to the fabric instance you can get the whole object state out of it with: var currentCanvasData = canvas.toObject(). You will get a nice javascript object that you can pass in later to recreate the canvas. canvas.clear() will remove everything from the canvas and canvas.loadFromJSON(currentCanvasData) will load it all back up. Make sure to call canvas.renderAll() after loadFromJSON.
this will make it easy to switch bewteen 3 different work spaces for you and the user.
var canvas = new fabric.Canvas('c');
var currentView = 1;
var view1Save = {};
var view2Save = {};
var view1 = document.querySelector('.one');
var view2 = document.querySelector('.two');
view1.onclick = (function() {
if (currentView === 2) {
view2Save = canvas.toObject();
canvas.clear();
canvas.loadFromJSON(view1Save, canvas.renderAll.bind(canvas));
currentView = 1;
view1.disabled = true
view2.disabled = false
}
});
view2.onclick = (function() {
if (currentView === 1) {
view1Save = canvas.toObject();
canvas.clear();
canvas.loadFromJSON(view2Save, canvas.renderAll.bind(canvas));
currentView = 2;
view1.disabled = false
view2.disabled = true
}
});
var addRect = document.querySelector('.rect');
addRect.onclick = (function() {
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: 'red'
}));
});
var addImg = document.querySelector('.image');
addImg.onclick = (function() {
var imgElement = new Image();
imgElement.src = 'http://cdn.sstatic.net/Sites/stackoverflow/img/sprites.png';
imgElement.onload = (function() {
canvas.add(new fabric.Image(imgElement, {
left: 100,
top: 100,
angle: 30,
opacity: 0.85
}));
});
});
canvas { border: 1px solid #ccc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
different views:
<button class="one" disabled>view 1</button>
<button class="two" >view 2</button>
<br />
<button class="rect">add rect</button>
<button class="image">add image</button>
<canvas id="c" width="600" height="600"></canvas>
I'm drawing an image onto the background of a canvas. This image refreshes every second, but the canvas needs to be redrawn more often than that, every 0.5 seconds. The problem I seem to be running into is that Safari flickers when updating the canvas because it appears to lose the image data from the bgImg variable. Firefox works as expected and no flicker occurs.
Does anyone have any clue as to how to avoid this? Am I missing something really obvious?
To test this code, simply paste the entire code below into an html file and load with your browser. What it should do is:
create a canvas
load an image every second
redraw the canvas every 500ms, using the image as a background.
Here's the code:
<html>
<head>
<script type="text/javascript">
var bgImg = new Image();
var firstload = false;
var cv;
var ctx;
var updateBackground = function() {
bgImg.onload = function() {
firstload = true;
console.log("image loaded. Dimensions are " + bgImg.width + "x" + bgImg.height);
}
bgImg.src = "http://mopra-ops.atnf.csiro.au/TOAD/temp.php?" + Math.random() * 10000000;
setTimeout(updateBackground, 1000);
}
var initGraphics = function() {
cv = document.getElementById("canvas");
ctx = cv.getContext("2d");
cv.width = cv.clientWidth;
cv.height = cv.clientHeight;
cv.top = 0;
cv.left = 0;
}
var drawStuff = function() {
console.log("Draw called. firstload = " + firstload );
ctx.clearRect(0, 0, cv.width, cv.height);
if ( firstload == true) {
console.log("Drawing image. Dimensions are " + bgImg.width + "x" + bgImg.height);
try {
ctx.drawImage(bgImg, 0, 0);
} catch(err) {
console.log('Oops! Something bad happened: ' + err);
}
}
setTimeout(drawStuff, 500);
}
window.onload = function() {
initGraphics();
setTimeout(updateBackground, 1000);
setTimeout(drawStuff, 500);
}
</script>
<style>
body {
background: #000000;
font-family: Verdana, Arial, sans-serif;
font-size: 10px;
color: #cccccc;
}
.maindisplay {
position:absolute;
top: 3%;
left: 1%;
height: 96%;
width: 96%;
text-align: left;
border: 1px solid #cccccc;
}
</style>
</head>
<body>
<div id="myspan" style="width: 50%">
<canvas id="canvas" class="maindisplay"></canvas>
</div>
</body>
</html>
Your problem is that you are drawing bgImg while it's loading. Webkit does not cache the old image data while loading a new image; it wipes out the image as soon as a new .src is set.
You can fix this by using two images, using the last-valid-loaded one for drawing while the next one is loading.
I've put an example of this online here: http://jsfiddle.net/3XW8q/2/
Simplified for Stack Overflow posterity:
var imgs=[new Image,new Image], validImage, imgIndex=0;
function initGraphics() {
// Whenever an image loads, record it as the latest-valid image for drawing
imgs[0].onload = imgs[1].onload = function(){ validImage = this; };
}
function loadNewImage(){
// When it's time to load a new image, pick one you didn't last use
var nextImage = imgs[imgIndex++ % imgs.length];
nextImage.src = "..."+Math.random();
setTimeout(loadNewImage, 2000);
}
function updateCanvas(){
if (validImage){ // Wait for the first valid image to load
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(validImage, 0, 0);
}
setTimeout(updateCanvas, 500);
}