Flicker on jQuery Background Change - javascript

I created a jsfiddle for my current code. http://jsfiddle.net/gL5sB/38/
I am trying to change the body background css on scroll event. When the background changes it appears to flicker when the css is updated and new image is loaded. At times it seems smooth and then it seems to get worse. Very strange. Curious if anyone knows how to optimize?
I am preloading the images. Not sure why the flicker. Any ideas?
$(document).ready(function () {
switchImage();
});
$(window).scroll(function () {
switchImage();
});
var pics = []; // CREATE PICS ARRAY
//PRELOAD FUNCTION TO SET UP PICS ARRAY IN MEMORY USING IMAGE OBJECT
function preload() {
for (i = 0; i < arguments.length; i++) {
pics[i] = new Image();
pics[i].src = arguments[i];
//alert("preload " + arguments[i]);
}
}
preload(
'bgImage/100.jpg',
'bgImage/101.jpg',
'bgImage/102.jpg',
'bgImage/103.jpg',
'bgImage/104.jpg',
'bgImage/105.jpg',
'bgImage/106.jpg',
'bgImage/107.jpg',
'bgImage/108.jpg',
'bgImage/109.jpg',
'bgImage/110.jpg',
'bgImage/111.jpg',
'bgImage/112.jpg',
'bgImage/113.jpg'
);
function switchImage() {
var s = $(window).scrollTop()/10;
var index = Math.floor(s / 5);
$('body').css('background-image', 'url(' + pics[index].src + ')');
}

Why don't you use one image (sprite image) and just move it with background-position instead of replacing the image?
(about the size - you can set percentage based background-size in your case - the height will be 1400%
Because your'e preloading all the images anyway - it won't cost you in page loading time - and it might also save some time because with the right compression 1 image of 14 will weight less then 14 images of 1

Chrome is OK. I can see the flicker in IE. A Solution for that is at the bottom of this post.
I suspect A video version would compress and load faster than all the images, but as suggested by #Allendar drawing it would be the most transmission efficient. I would suggest canvas or SVG.
Another way using images along would be to have individual components as either images or icon fonts placed on the display with absolute positioning and then just turning them on or off in script. But that's a very complex solution.
I think the simplest and fastest method for you to solve the issue today though would be to just tweak the solution you have. As other people have suggested, the multiple images approach won't be super efficient and if you're going to take that route at least make sure you set the caching headers on your web server to cache indefinitely;
Cache-Control: public;
Expires: Mon, 31 Dec 2035 12:00:00 GMT
OK, so the flicker problem is just a bug/inefficiency in the rendering engine in IE, so here's a workaround that uses a different approach.
Basically stretch an absolutely positioned DIV over the body, instead of using the body itself. In fact, in this case we use multiple DIVs, one for each image and overlay them on top of one another. You could create these nodes in script as well.
Secondly, add another DIV for your content, and overlay that over the body as well;
<body>
<div id="b100" class="background" style="background-image:url('http://ingodwetrustthemovie.com/bgImage/100.jpg')"></div>
<!-- rest omitted -->
<div id="b113" class="background" style="background-image:url('http://ingodwetrustthemovie.com/bgImage/113.jpg')"></div>
<div id="content">
<div id="test">test div</div>
here is some text
</div>
</body>
The simply show one at a time and hide the rest;
function switchImage() {
var s = $(window).scrollTop()/10;
var index = Math.floor(s / 5);
$('.background').hide();
$('.background').eq(index).show();
}
I had a suspicion that twiddling the css display option would be less flickery than changing the src attribute and it seems to do the trick.
Here's the Fiddle
Of course you may still need to optimise the code to allow for the first loaded image to be shown first instead of the plain background, but I think this demonstrates the principle of a fix.
You could also consider making a very large CSS Sprite, by bundling the images into one huge strip and then using background-position. That would probably work on the body tag itself then. Of course this would mean downloading a huge file before you can display any images at all, but there are two advantages;
One image (especially with such similarity) will compress way better than each individual one in isolation.
Using the same caching directives, that's only one HTTP/GET/302 cycle instead of 13 once you've fetched the image the first time, so your page may load faster still.
SVG
SVG elements work much like the DOM. If you can get your content delivered as an SVG you can drill into the graphic, locate the elements, give them IDs etc, and manipulate them much like you would any other DOM element;
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<ellipse id="e1" cy="420" cx="200" rx="420" ry="30" style="fill:3f5566" />
<ellipse id="e2" cy="420" cx="170" rx="390" ry="20" style="fill:4f5566" />
<ellipse id="e3" cy="420" cx="145" rx="370" ry="15" style="fill:5f5566" />
<ellipse id="e4" cy="420" cx="100" rx="370" ry="20" style="fill:6f5566" />
<ellipse id="e5" cy="420" cx="45" rx="300" ry="15" style="fill:8f5566" />
</svg>
Here's another fiddle that hides/unhides SVG elements based on a scroll.
Ideally, assuming they've generated that graphic in 'layers', try and have your designers deliver you an SVG where the layers are converted into groups. Adobe Illustrator can do that for instance.
Then you can easily turn off the layers/groups as necessary to create the animated effect.

If the images aren't really needed, you can create some system for yourself where you draw, and keep updating, the background using canvas. This would be start;
JSfiddle
HTML
<img id="template" src="http://ingodwetrustthemovie.com/bgImage/100.jpg" />
<canvas id="absBg" width="1000px" height="560px"></canvas>
CSS
body {
margin: 0px;
}
#template {
position: absolute;
width: 1000px;
height: 563px;
}
#absBg {
position: absolute;
/*background: rgba(0, 0, 0, 0.50);*/
/*height: 100%;*/
}
JavaScript/jQuery
'use strict';
function drawBlock(ctx, color, line_width, start, lines) {
ctx.lineWidth = line_width;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(start[0], start[1]);
for (var i = 0; i < lines.length; i++) {
ctx.lineTo(lines[i][0], lines[i][1]);
}
ctx.closePath();
ctx.stroke();
}
function drawBg() {
var absBg = document.getElementById('absBg');
var ctx = absBg.getContext('2d');
var demo_red = 'red';
var grey = '#28282';
var color = demo_red;
drawBlock(ctx, color, 1, [185, 87], [[205, 75], [226, 98], [207, 110]]);
drawBlock(ctx, color, 1, [235, 60], [[253, 50], [272, 71], [254, 81]]);
}
$(document).ready(function () {
drawBg();
// Scroll trigger
$(window).scroll(function () {
});
});
The demo uses the actual background image you have as a background-model, where you can draw over with the canvas. This way you can mimick the look of the original image you have.
You can try to manage some kind of "difference-array" where you store which blocks are different on which locations. This way you can trigger the function on scroll with certain parameters to let it change the drawing based on that.
I hope for you that you don't "need" the images per se. Drawing with canvas is so much faster than loading tons of images :)

Here is a solution that works (2014.7.11) at firefox 30.0, chrome 35.0, opera 22.0, ie 11.0:
STEP 1: add these lines at .htaccess:
# cache for images
<FilesMatch "\.(png)$">
Header set Cache-Control "max-age=10000, public"
</FilesMatch>
detailed description of this problem and how to fix it:
https://code.google.com/p/chromium/issues/detail?id=102706
STEP 2: add images preloading, for example:
var pics = []; // CREATE PICS ARRAY
$(document).ready(function(){
...
preload(
'/public/images/stars.red.1.star.png',
'/public/images/stars.red.2.star.png',
'/public/images/stars.red.3.star.png',
'/public/images/stars.red.4.star.png',
'/public/images/stars.red.5.star.png',
'/public/images/stars.empty.png'
);
...
$('.rating').on('mousemove', function(event){
var x = event.pageX - this.offsetLeft;
var id = getIdByCoord(x); //
if ($(this).data('current-image') != id) {
$(this).css('background-image', 'url(' + pics[id].src + ')');
$(this).data('current-image', id);
}
})
...
})
...
// PRELOAD FUNCTION TO SET UP PICS ARRAY IN MEMORY USING IMAGE OBJECT
function preload() {
for (i = 0; i < arguments.length; i++) {
pics[i] = new Image();
pics[i].src = arguments[i];
// alert("preload " + arguments[i]);
}
}
P.S. thanks Shawn Altman

Related

Cursor visual effect

I need help,
I just saw this main page on a great website www.snapplespiked.ca.
I really like so much this kind of cursor-waving effect, when you moving the cursor all over the page. I was trying this web on Atom/VsCode/Codepen and CAN NOT get these effects on the whole page. It appears only in first place on the main page when you just entering on website. In the moment when you scrolling a page down to see the rest of the parts on the page the effect disappears, not working.
I use the same HTML and CSS and all js scripts and there is not the same effect as on the original website.
What I did wrong?
Is anyone know what I did wrong ?
This effect come from this part of the code in HTML:
<div class="c-background js-app-background">
<div class="c-background__texture js-app-backgroundTexture">
<div data-product-id="795" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/mango.png);" data-color="#fc9a44" class=""></div>
<div data-product-id="804" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon_peel.png);" data-color="#ffe24a" class=""></div>
<div data-product-id="808" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/watermelon.png);" data-color="#4ca463" class="is-active"></div>
<div data-product-id="798" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/raspberry.png);" data-color="#ff3443" class=""></div>
<div data-product-id="806" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/strawberry.png);" data-color="#ff3443" class=""></div>
<div data-product-id="802" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon.png);" data-color="#ffe24a" class=""></div>
<div data-product-id="800" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/peach.png);" data-color="#fc9a44" class=""></div>
</div>
<canvas class="c-background__canvas js-app-backgroundCanvas" style="width: 784px; height: 779px;" width="1176" height="1168"></canvas>
</div>
<div class="c-background c-background--foreground js-app-background">
<div class="c-background__texture js-app-backgroundTexture">
<div data-product-id="795" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/mango.png);" data-color="#fc9a44" class="is-active"></div>
<div data-product-id="804" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon_peel.png);" data-color="#fc9a44" class=""></div>
<div data-product-id="808" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/watermelon.png);" data-color="#fc9a44" class=""></div>
<div data-product-id="798" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/raspberry.png);" data-color="#fc9a44" class=""></div>
<div data-product-id="806" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/strawberry.png);" data-color="#fc9a44" class=""></div>
<div data-product-id="802" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon.png);" data-color="#fc9a44" class=""></div>
<div data-product-id="800" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/peach.png);" data-color="#fc9a44" class=""></div>
</div>
</div>
You shouldn't directly copy code snippets from other websites, as you don't have the license to use them legally, and they're only really guarrenteed to be fit for their original purpose --- as you've found, it doesn't really work for your use case, and often it'll also result in a lot of redundant code.
In fact, this effect isn't particularlly hard, so lets try to re-create it ourselves!
Getting Started
The HTML5 canvas element lets us draw pixels onto it in various shapes. It's really useful for creating complex graphical effects like this one. The background of the canvas is transparent by default, and you can clear pixels off of it, so one way we could do this would be to fill the entire canvas white every frame, and then erase away the white where the cursor trails are to reveal the background underneath.
This means that we need to do a few things in our JavaScript:
Keep track of the user's mouse movements over the canvas, and an initial size property of the blob, which we'll reduce over time.
Every tick...
Fill the canvas with white
Loop over our list of blobs that we're keeping track of
For each one, erase a circle from the canvas centered from the mouse's position at that time and with a radius equal to the size that we're also keeping track of.
Decrement the size by some number so that next tick we draw the blob slightly smaller.
If the size is less than some number (zero, say), then stop tracking that blob anymore
Getting the <canvas> in place
We need to start by making the canvas and placing it on the page. Almost everything else will be done with JS.
<div id="page">
<canvas id="mouseTrailEffect"></canvas>
<div class="content"></div>
</div>
The page is the container for both the canvas and the content. The content div contains all of the other content which should appear over the top of the canvas.
We want to do a couple things with the CSS here too:
#page {
position: relative;
}
#page canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#page .content {
position: absolute;
}
This makes sure that it fills its entire parent, but remains sorted behind the rest of the content.
In the JavaScript, we now need to create a reference to the canvas, it's drawing context, and we also need to adjust its size, so that the width and height are set explictly --- even though this has been done with CSS, this will actually just stretch the canvas's pixels to cover the requested area, rather than adding more pixels to it to match the space it covers 1-to-1, which results in it looking quite blurry. We also need to make sure we adjust its size again whenever the user resizes the window, to make it responsive.
function resizeCanvas(cv) {
let rect = cv.getBoundingClientRect();
cv.width = rect.width;
cv.height = rect.height;
}
function setupMouseTrailEffect() {
let cv = document.getElementById("mouseTrailEffect");
let ctx = cv.getContext("2d");
resizeCanvas(cv);
window.addEventListener("resize", ()=>{
resizeCanvas(cv);
});
}
window.addEventListener("load", ()=>{ // When the page load is completed
setupMouseTrailEffect();
});
Keeping track of the mouse movements
Whenever the user moves their cursor over our canvas, we get a mousemove event fired on it that we can listen to. We can then extract the X and Y positions of the mouse from that event (relative to the page's scroll position and the canvas's offset from the top-left corner of the page), and create an object like to represent the blob.
{x: ____, y: ____, size: ____}
We can add those objects into an array, which I'll call blobs. One thing to note here is that we actually listen for the mousemove event on the canvas's parent, not the canvas itself. This is so that it gets called even if the user hovers over something else on the page (e.g. some content) which isn't directly the canvas itself.
let blobs = [];
const INITIAL_BLOB_SIZE = 50; // This is radius that blobs will be when they start out! You can change it to make them bigger / smaller.
document.getElementById("page").addEventListener("mousemove", (ev)=>{
blobs.push({
x: ev.pageX - cv.offsetLeft,
y: ev.pageY - cv.offsetTop,
size: INITIAL_BLOB_SIZE
});
});
Updating every tick
Now, we need to figure out how to update our canvas every tick. This can be done by first defining the function which should be run each tick, and then at the end of it telling the browser that the next animation frame it gets it should try and run our function again.
const tick = ()=>{
ctx.clearRect(0, 0, cv.width, cv.height);
ctx.fillStyle = "#ffffff"; // white
ctx.fillRect(0, 0, cv.width, cv.height); // fill the entire canvas with white
window.requestAnimationFrame(tick); // run this again ASAP
}
tick(); // Run the first tick to get it started!
Drawing the blobs
Now for the fun part!
Inside our tick function, after we fill everything white, lets loop through our blobs and erase them from the canvas. Note that the canvas works like e.g. Microsoft Paint does, in that there are no layers --- every pixel can have exactly one colour at one time. Filling it white sets them all to white, and to erase the area occupied by a blob we'll just draw a new shape there, and composite it so that it gets removed and the pixels are set to a fully transparent colour.
We also need to make sure that we adjust the size of the blobs each tick, and that we can remove them from the array once they reach a set minimum size. To do the latter, we'll make sure we loop through the array backwards, so that deleting one doesn't change the indexes that we're looping over.
const BLOB_DECAY_RATE = 2; // How many pixels to remove from the size of a blob each tick
const MIN_BLOB_SIZE = 0; // When a blob reaches this size, we stop tracking it and it disappears.
ctx.globalCompositeOperation = "destination-out"; // Instead of drawing these shapes, erase them instead!
for (let i = blobs.length - 1; i >= 0; i--) { // Loop over the list of blobs backwards
ctx.beginPath(); // Create a path which we'll erase
ctx.arc(blobs[i].x, blobs[i].y, blobs[i].size, 0, Math.PI * 2); // Add a arc to the path with is a circle, centered at the blob's X and Y positions, and with its size as the radius.
ctx.fill();
blobs[i].size -= BLOB_DECAY_RATE; // Adjust the blob's size
if (blobs[i].size <= MIN_BLOB_SIZE) {
blobs.splice(i, 1); // Remove the blob from the array
}
}
ctx.globalCompositeOperation = "source-out"; // Stop erasing when we draw
All together now
Here's all that code put together into a working example!
const INITIAL_BLOB_SIZE = 50; // This is radius that blobs will be when they start out! You can change it to make them bigger / smaller.
const BLOB_DECAY_RATE = 4; // How many pixels to remove from the size of a blob each tick
const MIN_BLOB_SIZE = 20; // When a blob reaches this size, we stop tracking it and it disappears.
function resizeCanvas(cv) {
let rect = cv.getBoundingClientRect();
cv.width = rect.width;
cv.height = rect.height;
}
function setupMouseTrailEffect() {
let cv = document.getElementById("mouseTrailEffect");
let ctx = cv.getContext("2d");
resizeCanvas(cv);
window.addEventListener("resize", ()=>{
resizeCanvas(cv);
});
let blobs = [];
document.getElementById("page").addEventListener("mousemove", (ev)=>{
blobs.push({
x: ev.pageX - cv.offsetLeft,
y: ev.pageY - cv.offsetTop,
size: INITIAL_BLOB_SIZE
});
});
const tick = ()=>{
ctx.globalCompositeOperation = "source-out"; // Stop erasing when we draw
ctx.clearRect(0, 0, cv.width, cv.height);
ctx.fillStyle = "#fff"; // white
ctx.fillRect(0, 0, cv.width, cv.height); // fill the entire canvas with white
ctx.globalCompositeOperation = "destination-out"; // Instead of drawing these shapes, erase them instead!
for (let i = blobs.length - 1; i >= 0; i--) { // Loop over the list of blobs backwards
ctx.beginPath(); // Create a path which we'll erase
ctx.arc(blobs[i].x, blobs[i].y, blobs[i].size, 0, Math.PI * 2); // Add a arc to the path with is a circle, centered at the blob's X and Y positions, and with its size as the radius.
ctx.fill();
blobs[i].size -= BLOB_DECAY_RATE; // Adjust the blob's size
if (blobs[i].size <= MIN_BLOB_SIZE) {
blobs.splice(i, 1); // Remove the blob from the array
}
}
window.requestAnimationFrame(tick); // Run this again ASAP!
}
tick(); // Run the first tick to get it started!
}
window.addEventListener("load", ()=>{ // When the page load is completed
setupMouseTrailEffect();
});
#page {
/** Gradient just so we can see the effect **/
background-image: linear-gradient(0deg, #f00 0%, #ff0 100%);
border: solid 1px #000;
height: 250px;
position: relative;
padding: 10px;
}
#page canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#page .content {
position: absolute;
}
<div id="page">
<canvas id="mouseTrailEffect"></canvas>
<div class="content">
<h1>Some content</h1>
Stuff above the canvas.
</div>
</div>
You can tweak the three constants, INITIAL_BLOB_SIZE, BLOB_DECAY_RATE and MIN_BLOB_SIZE to make the effect closer to what you want (and I've moved them all together to the top of the snippet to make that easy :D).

can't really resize a canvas image

When learning about HTML canvas, I found this
Example: Dynamically change images
Basically dynamically change image from colors to gray-scale and vice versa using HTML Canvas. This example select img elements by class names ('grayscale') so to be able to apply this process to multiple images at once.
HTML
<img class="grayscale" src="myPicture.png" alt="Description of my picture" />
JavaScript
window.addEventListener('load', removeColors);
function showColorImg() {
this.style.display = 'none';
this.nextSibling.style.display = 'inline';
}
function showGrayImg() {
this.previousSibling.style.display = 'inline';
this.style.display = 'none';
}
function removeColors() {
var aImages = document.getElementsByClassName('grayscale'),
nImgsLen = aImages.length,
oCanvas = document.createElement('canvas'),
oCtx = oCanvas.getContext('2d');
for (var nWidth, nHeight, oImgData, oGrayImg, nPixel, aPix, nPixLen, nImgId = 0; nImgId < nImgsLen; nImgId++) {
oColorImg = aImages[nImgId];
nWidth = oColorImg.offsetWidth;
nHeight = oColorImg.offsetHeight;
oCanvas.width = nWidth;
oCanvas.height = nHeight;
oCtx.drawImage(oColorImg, 0, 0);
oImgData = oCtx.getImageData(0, 0, nWidth, nHeight);
aPix = oImgData.data;
nPixLen = aPix.length;
for (nPixel = 0; nPixel < nPixLen; nPixel += 4) {
aPix[nPixel + 2] = aPix[nPixel + 1] = aPix[nPixel] = (aPix[nPixel] + aPix[nPixel + 1] + aPix[nPixel + 2]) / 3;
}
oCtx.putImageData(oImgData, 0, 0);
oGrayImg = new Image();
oGrayImg.src = oCanvas.toDataURL();
oGrayImg.onmouseover = showColorImg;
oColorImg.onmouseout = showGrayImg;
oCtx.clearRect(0, 0, nWidth, nHeight);
oColorImg.style.display = "none";
oColorImg.parentNode.insertBefore(oGrayImg, oColorImg);
}
}
So far so good, the example works well except that when I use a big image, it makes the horizontal scroll bar appears (image width is 2400px)
so I've opted to precise the width of the image in the img tag as in here:
<img class="grayscale" src="mypic3.jpg" width="698px" alt="Description of my picture" />
So that also works for the original colorful image, but for the modified one (gray-scale one) it seems to map only a portion of the image to the designed width. so instead of getting this image:
I'm getting only this one :
Notice that this happens even if oCanvas.width = nWidth;initialize canvas width with the offset of the img tag (that is 689)
Other useless things that I've tried :
oCtx.drawImage(oColorImg, 0, 0);
oCanvas.style.backgroundSize="70%";
oCanvas.style.width="689px";
oCtx.putImageData(oImgData, 0, 0, 0, 0, 698, 410);
I appreciate any help
Edit: for the marks as duplicate
- For the one who marked the question as duplicate of question answered by just setting the canvas width, I specifically declared that it didn't work and the code above include setting canvas width.
- For the last four ones, which use the extended form of drawimage(), I did try it but it didn't work unless I set an explicit global CSS rule setting the img tag width. So I'm not sure what is the mechanics behind that. But in this case, if I don't put this rule the problem persist. So in my humble opinion, adding this particular information my help people stuck in a similar problem. as well as any explanation on why or how this works. thanks
The answer consists of two steps :
The first (as Kaiido suggests) is making use of the extended syntax of drawImage() taking the canvas.width,canvas.height parameters into account
oCtx.drawImage(oColorImg, 0, 0, nWidth, nHeight);
The second is that you make a CSS rule setting images width
(notice that if you don't, the problem persists even if the image seems to have the width set normally both in display and by extracting image width with Javascript. So one way to improve this answer is to explain this strange behavior)
img{
width: 689px;
}

Find an image in a canvas by coordinates

Edit:
1) basically I would like to know if there is a way to know if in an html canvas there is an image in a given point.
var point = [5, 8]
there is an image on that point? there is a command for find out?
2) the canvas is an hex map so the images are on regular positions in the grid. They can't be halfway. So a generic point can be brought back to the insertion point for the image and the question become: there is a (fast) way to know if in an html canvas there is an image with a given insertion point?
3) can help if I'm using Raphael?
4) I have a working solution: to confront the (x, y) attributes (insertion point) of ALL the images with the coordinates of point, but it's very slow. I'm looking for something faster. If you are interessed in my solution, read below. Comment and improvement on my solution are welcome but my question is different: I'm looking for a different solution and specifically I'm looking for the answers to my questions above.
/edit
I have an hex map and I want to find which images are in an exe given the exe coordinates.
My code is working but I hope for something more efficient.
To be precise when i click on an exe I want to know if in the six border exes there is a specific image. Read the comment in the code
My javascript:
var paper_map = Raphael(canvas_map, '100%', '100%');
var Image = paper_map.image(path_image, x-16, y-14, width, high);
[...]
var border_list=find_border_list(col, row)
//find_border_list function gives a list of coordinates for the 6 border exes [[c1, r1], ... [c6, r6]]
var my_exe_list=[];
for (var i=0; i<border_list.length; i++ ) {
var coord = number_to_coord(border_list[i][0], border_list[i][1]);
//number_to_coord function transforms coordinates (column, row) in (x,y)
var x = coord[0]-16; //I find the coordinates of the 'corner' of the exe
var y = coord[1]-14;
//each image is drawed starting from a 'corner' of an exe
var child_list=canvas_map.lastElementChild.childNodes;
//here's my problem: this list is huge because it contains all the images of the canvas!!
for (var j=2; j<child_list.length; j++){
if (child_list[j].tagName == 'image') {
if (child_list[j].attributes.x.value==x &&
child_list[j].attributes.y.value==y &&
child_list[j].href.baseVal.slice(child_list[j].href.baseVal.lastIndexOf('/')+1).startsWith('mystring')) {
//the first two conditions find all the images on my exe, the third find which I'm interessed in (based on the name)
my_exe_list.push(child_list[j])
};
};
};
};
My html:
<div id='canvas_map' class='canvas can_map' style='position:absolute; left:133px; top:0px;'></div>
console.log(canvas_map) = <div id="canvas_map" class="canvas can_map" style="position: absolute; left: 133px; top: 0px; height: 520px; width: 1371px;">
console.log(child_list) = NodeList [ <desc>, <defs>, <image>, <image>, <image> ... ]
console.log(child_list[j]) = <image x="53" y="56" width="33" height="27" preserveAspectRatio="none" href="/my/path/image_name.png">
So the problem is I cycle six times in a very long list (could be some thousands of images):
var child_list=canvas_map.lastElementChild.childNodes;
Just to know I draw the images with Raphael and I can use jquery.

Clipping mask using fabricjs

I'm currently working on web app for photo editing using FabricJS and one of features I need to implement is something like Clipping masks from Photoshop.
For example I have this assets: frame, mask and image. I need to insert image inside frame and clip it with mask. Most tricky part is in requirements:
User should be able to modify image inside frame, e.g. move, rotate, skew... Frame itself also can be moved inside canvas.
Number of layers is not limited so user can add objects under or above masked image.
Masks, frames and images is not predefined, user should be able to upload and use new assets.
My current solution is this:
Load assets
Set globalCompositeOperation of image to source-out
Set clipTo function for image.
Add assets on canvas as a group
In this solution clipTo function preserve image inside rectangular area of frame and with help of globalCompositeOperation I'm clipping image to actual mask. At first sight it works fine but if I add new layer above this newly added group it will be cutted off because of globalCompositeOperation="source-out" rule. I've created JSFiddle to show this.
So, that else could I try? I've seen some posts on StackOverflow with advices to use SVGs for clipping mask, but if I understand it correctly SVG must contain only one path. This could be a problem because of third requirement of my app.
Any advice in right direction will help, because right now I'm totally stuck with this problem.
You can do this by using ClipPath property of Img Object which you want to mask. With this, you can Mask Any Type of Object. and also you need to add some Ctx Configuration in ClipTo function of Img Object.
check this link https://jsfiddle.net/naimsajjad/8w7hye2v/8/
(function() {
var img01URL = 'http://fabricjs.com/assets/printio.png';
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var img03URL = 'http://fabricjs.com/assets/ladybug.png';
var img03URL = 'http://fabricjs.com/assets/ladybug.png';
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = "red";
canvas.setHeight(500);
canvas.setWidth(500);
canvas.setZoom(1)
var circle = new fabric.Circle({radius: 40, top: 50, left: 50, fixed: true, fill: '', stroke: '1' });
canvas.add(circle);
canvas.renderAll();
fabric.Image.fromURL(img01URL, function(oImg) {
oImg.scale(.25);
oImg.left = 10;
oImg.top = 10;
oImg.clipPath = circle;
oImg.clipTo = function(ctx) {
clipObject(this,ctx)
}
canvas.add(oImg);
canvas.renderAll();
});
var bili = new fabric.Path('M85.6,606.2c-13.2,54.5-3.9,95.7,23.3,130.7c27.2,35-3.1,55.2-25.7,66.1C60.7,814,52.2,821,50.6,836.5c-1.6,15.6,19.5,76.3,29.6,86.4c10.1,10.1,32.7,31.9,47.5,54.5c14.8,22.6,34.2,7.8,34.2,7.8c14,10.9,28,0,28,0c24.9,11.7,39.7-4.7,39.7-4.7c12.4-14.8-14-30.3-14-30.3c-16.3-28.8-28.8-5.4-33.5-11.7s-8.6-7-33.5-35.8c-24.9-28.8,39.7-19.5,62.2-24.9c22.6-5.4,65.4-34.2,65.4-34.2c0,34.2,11.7,28.8,28.8,46.7c17.1,17.9,24.9,29.6,47.5,38.9c22.6,9.3,33.5,7.8,53.7,21c20.2,13.2,62.2,10.9,62.2,10.9c18.7,6.2,36.6,0,36.6,0c45.1,0,26.5-15.6,10.1-36.6c-16.3-21-49-3.1-63.8-13.2c-14.8-10.1-51.4-25.7-70-36.6c-18.7-10.9,0-30.3,0-48.2c0-17.9,14-31.9,14-31.9h72.4c0,0,56-3.9,70.8,26.5c14.8,30.3,37.3,36.6,38.1,52.9c0.8,16.3-13.2,17.9-13.2,17.9c-31.1-8.6-31.9,41.2-31.9,41.2c38.1,50.6,112-21,112-21c85.6-7.8,79.4-133.8,79.4-133.8c17.1-12.4,44.4-45.1,62.2-74.7c17.9-29.6,68.5-52.1,113.6-30.3c45.1,21.8,52.9-14.8,52.9-14.8c15.6,2.3,20.2-17.9,20.2-17.9c20.2-22.6-15.6-28-16.3-84c-0.8-56-47.5-66.1-45.1-82.5c2.3-16.3,49.8-68.5,38.1-63.8c-10.2,4.1-53,25.3-63.7,30.7c-0.4-1.4-1.1-3.4-2.5-6.6c-6.2-14-74.7,30.3-74.7,30.3s-108.5,64.2-129.6,68.9c-21,4.7-18.7-9.3-44.3-7c-25.7,2.3-38.5,4.7-154.1-44.4c-115.6-49-326,29.8-326,29.8s-168.1-267.9-28-383.4C265.8,13,78.4-83.3,32.9,168.8C-12.6,420.9,98.9,551.7,85.6,606.2z',{top: 0, left: 180, fixed: true, fill: 'white', stroke: '', scaleX: 0.2, scaleY: 0.2 });
canvas.add(bili);
canvas.renderAll();
fabric.Image.fromURL(img02URL, function(oImg) {
oImg.scale(0.5);
oImg.left = 180;
oImg.top = 0;
oImg.clipPath = bili;
oImg.clipTo = function(ctx) {
clipObject(this,ctx)
}
canvas.add(oImg);
canvas.renderAll();
});
function clipObject(thisObj,ctx)
{
if (thisObj.clipPath) {
ctx.save();
if (thisObj.clipPath.fixed) {
var retina = thisObj.canvas.getRetinaScaling();
ctx.setTransform(retina, 0, 0, retina, 0, 0);
// to handle zoom
ctx.transform.apply(ctx, thisObj.canvas.viewportTransform);
thisObj.clipPath.transform(ctx);
}
thisObj.clipPath._render(ctx);
ctx.restore();
ctx.clip();
var x = -thisObj.width / 2, y = -thisObj.height / 2, elementToDraw;
if (thisObj.isMoving === false && thisObj.resizeFilter && thisObj._needsResize()) {
thisObj._lastScaleX = thisObj.scaleX;
thisObj._lastScaleY = thisObj.scaleY;
thisObj.applyResizeFilters();
}
elementToDraw = thisObj._element;
elementToDraw && ctx.drawImage(elementToDraw,
0, 0, thisObj.width, thisObj.height,
x, y, thisObj.width, thisObj.height);
thisObj._stroke(ctx);
thisObj._renderStroke(ctx);
}
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
Not sure what you want.
If you want the last image loaded (named img2), the one you send to the back to not effect the layers above do the following.
You have mask,frame,img, and img2;
Put them in the following order and with the following comp settings.
img2, source-over
img, source-over
mask, destination-out
frame, source-over
If you want something else you will have to explain it in more detail.
Personally when I provide masking to the client I give them full access to all the composite methods and allow them to work out what they need to do to achieve a desired effect. Providing a UI that allows you to change the comp setting, and layer order makes it a lot easier to sort out the sometimes confusing canvas composite rules.
I'd suggest looking at this solution.
Multiple clipping areas on Fabric.js canvas
You end up with a shape layer that is used to define the mask shape. That shape then gets applied as a clipTo to your image.
The one limitation I can think off though that you might run into is when you start to rotate various shapes. I know I have it working great with a rectangle and a circle, however ran into some issues with polygons from what I recall... This was all setup under and older version of FabricJS however, so there may have been some improvements there that I'm not experienced with.
The other issue I ran into was drop shadows didn't render correctly when passed to a NodeJS server running FabricJS.

Getting original image for cropping, ignoring the width attribute?

My user can upload really big images, and for cropping and display purposes i'm adding width attribute so it will fit well in the browser window. Real image size can be - say 1920 x 1080 px.
<!-- width added for display purpose -->
<img class="croppable" src="images/fhd.jpg" width="640" />
In order to calculate real selection box dimension (if the x coordinate is 20px then would be 60px in the original full hd picture) i need to get the full image size before apply the width attribute.
The problem is that this will return 640 as value, taking into account the width attribute:
// Important: Use load event to avoid problems with webkit browser like safari
// when using cached images
$(window).load(function(){
$('img.croppable').each(function(){
alert(this.width);
});
});
Please don't flag this as duplicate since what i'm asking is completly different from simple image width/height retrival (which works, actually).
EDIT: Chris G. solution seems not working:
$(window).load(function(){
$('img.croppable').each(function(){
console.log(this.src);
var original = new Image(this.src);
console.log(original);
$("#original_w").text(original.width); // Temp, more images to be added
$("#original_h").text(original.height); // Temp, more images to be added
});
});
Console output:
http://localhost/DigitLifeAdminExtension/images/pillars-of-creation.jpg
<img width="0">
Get the width/height of the image itself, not the div it is contained within.
$(window).load(function(){
$('img.croppable').each(function(){
var img = new Image();
img.src = $(this).src;
alert(img.width);
});
});
You can remove the attributes, get the width and put the attributes in place again:
var $img = $(img);
var oldWidth = $img.attr("width");
var imgWidth = $img.removeAttr("width").width();
$img.width(oldWidth);
But I think Chris G.'s answer works well too, just making sure it will be loaded when you try to get the width:
img.onload = function() {
if (!img.complete) return; // IMG not loaded
width = img.width;
imgManipulationGoesHere();
}
Works in most up-to-date browsers and IE9.
$(window).load(function(){
$('img.croppable').each(function(){
alert(this.naturalHeight);
});
});
The working solution would be:
$(function(){
$('img.croppable').each(function () {
var original = new Image(this.src);
original.onload = function () {
alert(original.src + ': ' + original.width + 'x' +original.height);
};
});
});

Categories