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).
Related
I have a simple functioning 2d HTML canvas game, at the moment you can see the whole map, I want there to be a scrolling camera for the canvas game so you can't see the entire map at once, I have no idea how to do this. I've Googled a bit, found nothing.
ctx.drawImage(character,x,y);
canvas already correctly setUp
<canvas id="canvas"></canvas>
There’s no errors or bugs
2 way :
1) adjust the positon of the object to draw with the camera offset:
deltaX=object.x-cameraX
deltaY=object.y-cameraY
if(deltaX + object.width > 0
&& deltaX - object.width < cameraWidth
&&deltaY + object.height > 0
&& deltaY - object.height < cameraHeight){
ctx.drawImage(character,deltaX,deltaY);
}
2) have two context and print one into the other
ctxCamera.drawImage(ctx,cameraX,cameraY);
I don't know if I'm too late, but you can encompass the canvas tags with div tags.
HTML:
<div id = "viewport">
<canvas id="canvas"></canvas>
</div>
CSS:
#viewport{
overflow: hidden; //so you can't see outside of the div
width: /*insert desired amount*/px;
height: /*insert desired amount*/px;
}
Javascript:
function scrollCamera(camx, camy){
var viewport = document.getElementById('viewport');
viewport.scrollTop = camy;
viewport.scrollLeft = camx;
}
scrollCamera(x-/*desired amount*/, y-/*desired amount*/); //makes the camera follow the player
I would like show a cutting of a canvas element in another canvas element. For explanation i have the following structure:
Canvas Element which gets filled it's background by an image. On top of this image i draw a arrow and a possible path. The background-image is really big, which means that i can not get that much Information from this big image. This is the reason for point two.
I would like to show a cutting of the canvas element 1. For example like the following image:
Currently i get the coordinate of the red arrow in canvas element 1, now i would like to do something like a cutting of this section with offset like in the image.
How could i solve something like this with JavaScript / JQuery. In summary i have two canvas elements. One of them is showing a big map with a red arrow which represents the current location (this works already), but now i wanna show a second canvas element with the zoom of this section where the red arrow is. Currently i am getting the coordinates, but no idea how i could "zoom" into an canvas element.
Like some of the current answers said, i provide some code:
My HTML Code, there is the mainCanvasMap, which has a Background Image and there is the zoomCanvas, which should display a section of the mainCanvasMap!
Here is a JavaScript snippet, which renders the red arrow on the map and should provide a zoom function (where the red-arrow is located) to the zoomCanvas Element.
var canvas = {}
canvas.canvas = null;
canvas.ctx = null;
canvas.scale = 0;
var zoomCanvas = {}
zoomCanvas.canvas = null;
zoomCanvas.ctx = null;
zoomCanvas.scale = 0;
$(document).ready(function () {
canvas.canvas = document.getElementById('mainCanvasMap');
canvas.ctx = canvas.canvas.getContext('2d');
zoomCanvas.canvas = document.getElementById('zoomCanvas');
zoomCanvas.ctx = zoomCanvas.canvas.getContext('2d');
setInterval(requestTheArrowPosition, 1000);
});
function requestTheArrowPosition() {
renderArrowOnMainCanvasElement();
renderZoomCanvas();
}
function renderArrowOnMainCanvasElement(){
//ADD ARROW TO MAP AND RENDER THEM
}
function renderZoomCanvas() {
//TRY TO ADD THE ZOOM FUNCTION, I WOULD LIKE TO COPY A SECTION OF THE MAINCANVASMAP
zoomCanvas.ctx.fillRect(0, 0, zoomCanvas.canvas.width, zoomCanvas.canvas.height);
zoomCanvas.ctx.drawImage(canvas.canvas, 50, 100, 200, 100, 0, 0, 400, 200);
zoomCanvas.canvas.style.top = 100 + 10 + "px"
zoomCanvas.canvas.style.left = 100 + 10 + "px"
zoomCanvas.canvas.style.display = "block";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<!--MY MAIN CANVAS ELEMENT, WHICH HAS A BACKGROUND IMAGE AND WHERE THE ARROW IS RENDEREED-->
<canvas id="mainCanvasMap" style="width:100%; height:100%; background: url('https://image.jimcdn.com/app/cms/image/transf/dimension=624x10000:format=jpg/path/s7d1eecaa5cee1012/image/i4484f962de0bf3c2/version/1543751018/image.jpg') 0% 0% / 100% 100%;"></canvas>
<!-- MY ZOOM CANVAS ELEMENT, SHOULD SHOW A CUTTING OF THE MAINCANVASMAP -->
<canvas id="zoomCanvas" style="height:100%;width:100%"></canvas>
The code is only a pseudo-code, but it shows what i like to do.
Your code is using css for the canvas image, that not always looks the way we think...
I will recommend you to draw everything from scratch, here is a starting point:
canvas = document.getElementById('mainCanvasMap');
ctx = canvas.getContext('2d');
zoomCanvas = document.getElementById('zoomCanvas');
zoomCtx = zoomCanvas.getContext('2d');
var pos = {x:0, y:40}
image = document.getElementById('source');
image.onload = draw;
function draw() {
ctx.drawImage(image,0,0);
setInterval(drawZoom, 80);
}
function drawZoom() {
// simple animation on the x axis
x = Math.sin(pos.x++/10 % (Math.PI*2)) * 20 + 80
zoomCtx.drawImage(image, x, pos.y, 200, 100, 0, 0, 400, 200);
}
<canvas id="mainCanvasMap"></canvas>
<canvas id="zoomCanvas"></canvas>
<img id="source" src="https://image.jimcdn.com/app/cms/image/transf/dimension=624x10000:format=jpg/path/s7d1eecaa5cee1012/image/i4484f962de0bf3c2/version/1543751018/image.jpg" style="display:none">
For example I have a limited canvas, smaller width/height than the uploaded image in it.
Guys how to make the effect of moving the image in the canvas window? In other words, the canvas window does not change, and the picture we "run". thanks
Animation basic movement
Like all animation to make something appear as if it moves is to draw a sequence of still images (a frame), each image slightly different. If the rate of frames are high enough (over about 20 per second) the human eye and mind see the sequence of still images as a continuous movement. From the first movies to today's high end games this is how animation is done.
So for the canvas the process of drawing a frame is simple. Create a function that clears the canvas, draws what you need, exit the function so that the browser can move the completed frame to the display. (Note that while in the function anything draw on the canvas is not seen on the display, you must exit the function to see what is drawn)
To animate you must do the above at at least more than 20 times a seconds. For the best results you should do it at the same rate as the display hardware shows frames. For the browser that is always 60 frames per second (fps).
Animation in the browser
To help sync with the display you use the function requestAnimationFrame(myDrawFunction) it tells the browser that you are animating, and that the results of the rendering should be displayed only when the display hardware is ready to show a new complete frame, not when the draw function has exited (which may be halfway through a hardware frame).
Animation Object
So as a simple example let's create a animation object.
const imageSrc = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const myImage = {
posX: 0, // current position of object
posY: 0,
speed: 3, // speed in pixels per frame (1/60th second)
direction: 0, // direction of movement in radians 0 is at 3oclock
image: (() => { // create and load an image (Image will take time to load
// and may not be ready until after the code has run.
const image = new Image;
image.src = imageSrc;
})(),
Draw function
Then a draw function that draws the object on the canvas
draw(ctx) {
ctx.drawImage(this.image, this.posX, this.posY);
},
Update function
As we are animating we need to move the object once per frame, to do this we create a update function.
update() {
this.posX += (mx = Math.cos(this.direction)) * this.speed;
this.posY += (my = Math.sin(this.direction)) * this.speed;
}
} // end of object
Many times a second
To do the animation we create a main loop that is call for every hardware display frame via requestAnimationFrame.
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// update the obj
myImage.update();
// draw the object
myImage.draw(ctx);
// request the next frame
requestAnimationFrame(mainLoop);
// note all of the above code is not seen by the use until the
// next hardwares display frame. If you used setTimeout or setInterval
// then it would be displayed when this function exits (which may be
// halfway through a frame resulting in the animation being cut in two)
}
// request the first frame
requestAnimationFrame(mainLoop);
Some extras
And that is the most basic animation. of course you need to get the canvas context and wait for the image to load. Also because the image moves of the canvas you would need to check when it does and either stop the animation.
Eg to stop animation is image is off screen
if(myImage.posX < canvas.width){ // only render while image is on the canvas
requestAnimationFrame(mainLoop);
} else {
console.log("Animation has ended");
}
Now to put it together as a demo.
The demo
The demo has some extra smarts to make the image wrap around, ensure that the image has loaded before starting and make it start off screen, but is basicly the same as outlined above.
// get the 2D context from the canvas id
const ctx = canvas.getContext("2d");
// setup the font and text rendering
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// create the image object and load the image
const imageSrc = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const myImage = {
posX: 0, // current position of object
posY: 0,
speed: 3, // speed in pixels per frame (1/60th second)
direction: 0, // direction of movement in radians 0 is at 3oclock
image: (() => { // create and load an image (Image will take time to load
// and may not be ready until after the code has run.
const image = new Image;
image.src = imageSrc;
// to start move the image of the display
image.onload = function(){
const imageDiagonalSize = Math.sqrt(
image.width * image.width + image.height * image.height
)
myImage.posX = (canvas.width / 2) - imageDiagonalSize - Math.cos(myImage.direction) * imageDiagonalSize;
myImage.posX = (canvas.height / 2) - imageDiagonalSize - Math.sin(myImage.direction) * imageDiagonalSize;
}
return image;
})(),
draw(ctx) {
ctx.drawImage(this.image, this.posX, this.posY);
},
update() {
var mx,my; // get movement x and y
this.posX += (mx = Math.cos(this.direction)) * this.speed;
this.posY += (my = Math.sin(this.direction)) * this.speed;
// if the image moves of the screen move it to the other side
if(mx > 0) { // if moving right
if(this.posX > canvas.width){
this.posX = 0-this.image.width;
}
}else if(mx < 0) { // if moving left
if(this.posX + this.image.width < 0){
this.posX = canvas.width;
}
}
if(my > 0) { // if moving down
if(this.posY > canvas.height){
this.posY = 0-this.image.height;
}
}else if(my < 0) { // if moving up
if(this.posY + this.image.height < 0){
this.posY = canvas.height;
}
}
}
}
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
if(myImage.image.complete) { // wait for image to load
myImage.update();
myImage.draw(ctx);
}else{ // some feedback to say the image is loading
ctx.fillText("Loading image..",canvas.width / 2, canvas.height / 2);
}
// request the next frame
requestAnimationFrame(mainLoop);
}
// request the first frame
requestAnimationFrame(mainLoop);
canvas {
border: 2px solid black;
}
<!-- id's must be unique to the page -->
<canvas id="canvas"></canvas>
Please note that the above code uses ES6 and will need a code pre processor like Babel to run on legacy browsers.
Check out this answer: https://stackoverflow.com/a/30739547/3200577
Basically, the way that html canvas works is very different from how html elements are rendered and painted. Whereas you can select and move an html element on the page, you cannot select and move something that you have added to a canvas because all you can do on a canvas is add and clear pixels. So, when you add an image to a canvas, you are adding the pixels of the image to the canvas. If you were to add the image again but a little to the left, then it would look like you've added two images, where the second one overlaps the first, which is not what you want.
So, to animate the motion of an image on the canvas, you need to:
choose an x and y as the position of the image on the canvas
draw the image on the canvas at x and y
increment the values of x and y to the new position that you want
clear the canvas
redraw the image at the new x and y
A more abstract description of this flow: basically, you need to create, store, and manage your own model of what your canvas looks like; when you want to add, remove of change things that you've painted on the canvas, you actually aren't going to be adding, removing, or changing anything directly on the canvas. You would add, remove and change things in your own model, clear the canvas, and then redraw the canvas on the basis of your model.
For instance, your model might be a JS object such as
myModel = {
images: [
{ url: "my/picture.png", position: [123,556] },
{ url: "another/picture.jpg", position: [95,111] }
]
}
and you would write functions for 1) incrementing the values of the positions of the images in the model, 2) clearing the canvas, and 3) drawing your model onto the canvas. Then, you would create a loop (using requestAnimationFrame or setInterval) that would repeatedly execute those three functions.
For large or complex projects, I strongly recommend using a canvas library such as paperjs, which implements that flow for you (so that you don't have to think about creating a model and clearing and redrawing the canvas). It provides high-level functionality such as animation right out of the box.
One way I can think of is with an animated svg, but there is probably a better way. What would you do if you had to animate these wavy blobs (mobile compatible)
Link to the only pin I've found similar
var wave = document.createElement("div");
wave.className += " wave";
docFrag.appendChild(wave);
wave.style.left = i * waveWidth + "px";
wave.style.webkitAnimationDelay = (i / 100) + "s";
Touch interaction would be nice too. Would there be any problems with canvas stuff ?
Here's an implementation of #DA.'s good answer:
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var cw=canvas.width;
var ch=canvas.height;
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.font='16px verdana';
ctx.lineWidth=5;
ctx.strokeStyle='white';
ctx.fillStyle='white';
var offsetX=0;
var bk=makeWave(canvas.width,canvas.height-120,10,2,'lightskyblue','cornflowerblue');
requestAnimationFrame(animate);
function animate(time){
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(bk,offsetX,0);
ctx.fillStyle='white';
ctx.font='18px verdana';
ctx.fillText('Multiple Lists',cw/2,30);
ctx.strokeRect(cw/2-50,85,100,50);
ctx.fillStyle='gray';
ctx.font='12px verdana';
ctx.fillText('You can create and save multiple ...',cw/2,250);
offsetX-=1;
if(offsetX< -bk.width/2){offsetX=0;}
requestAnimationFrame(animate);
}
function makeWave(width,midWaveY,amplitude,wavesPerWidth,grad0,grad1){
var PI2=Math.PI*2;
var totValue=PI2*wavesPerWidth;
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
c.width=width*2;
c.height=midWaveY+amplitude;
var grad=ctx.createLinearGradient(0,0,0,midWaveY);
grad.addColorStop(0.00,grad0);
grad.addColorStop(1.00,grad1);
//
ctx.beginPath();
ctx.moveTo(0,0);
for (x=0;x<=200;x++) {
var n=totValue*x/100;
ctx.lineTo(width*x/100,Math.sin(n)*amplitude+midWaveY);
}
ctx.lineTo(c.width,0);
ctx.closePath();
ctx.fillStyle=grad;
ctx.fill();
return(c);
}
body{ background-color:white; }
canvas{border:1px solid red; margin:0 auto; }
<canvas id=canvas width=300 height=300></canvas>
I'd make the wave a PNG (bottom solid gray, top transparent). Place it in a div twice the width of the card, and place that in a div the width of the card (this second div is the 'mask').
Then via CSS, have the nested give transform on the x axis to animate it sideways.
You shouldn't need any JS for this.
I would do this:
on page load create an off-screen canvas (just set display: none)
in a for loop compute the wave:
clear with transparency
paint only the white part because the colored part has a gradient
after each paint, get the PNG data out of the canvas and store it in an array
after the loop you will have an array of PNG images (frames)
cycle through those frames without recomputing the wave over and over again
This requires the wave to have a period that is affine to the number of frames you take (say a 2 second animation at 10 Hz would require 20 frames to be cyclic)
To be honest, you could store that server-side and just download it, without computing it client-side. Those PNG images would be very tiny because there isn't any color involved (just transparent/white/alpha channel). There are optimal settings for this, I guesstimate some 1KB per frame would suffice, that's a tiny 20 KB of images).
I am looking for a way to wrap a bitmap image around the canvas, for an infinite scrolling effect. I'm looking at EaselJS but clean javascript code will also suffice.
Right now I am displacing an image to the left, and when it reaches a certain mark, it resets itself.
Coming from actionscript, there was an option to "wrap" the pixels of a bitmap around to the other side, thereby never really displacing the image, instead you were wrapping the pixels inside the image. Is this possible in javascript with canvas?
My current code:
this.update = function() {
// super large graphic
_roadContainer.x -= 9;
if(_roadContainer.x < -291) _roadContainer.x = 0;
}
Start with a good landscape image.
Flip the image horizontally using context.scale(-1,1).
Combine the flipped image to the right side of the original image.
Because we have exactly mirrored the images, the far left and right sides of the combined image are exactly the same.
Therefore, as we pan across the combined image and “run out of image”, we can just add another copy of the combined image to the right side and we have infinite + seamless panning.
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/ywDp5/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// thanks Paul Irish for this RAF fallback shim
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var infiniteImage;
var infiniteImageWidth;
var img=document.createElement("img");
img.onload=function(){
// use a tempCanvas to create a horizontal mirror image
// This makes the panning appear seamless when
// transitioning to a new image on the right
var tempCanvas=document.createElement("canvas");
var tempCtx=tempCanvas.getContext("2d");
tempCanvas.width=img.width*2;
tempCanvas.height=img.height;
tempCtx.drawImage(img,0,0);
tempCtx.save();
tempCtx.translate(tempCanvas.width,0);
tempCtx.scale(-1,1);
tempCtx.drawImage(img,0,0);
tempCtx.restore();
infiniteImageWidth=img.width*2;
infiniteImage=document.createElement("img");
infiniteImage.onload=function(){
pan();
}
infiniteImage.src=tempCanvas.toDataURL();
}
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/mountain.jpg";
var fps = 60;
var offsetLeft=0;
function pan() {
// increase the left offset
offsetLeft+=1;
if(offsetLeft>infiniteImageWidth){ offsetLeft=0; }
ctx.drawImage(infiniteImage,-offsetLeft,0);
ctx.drawImage(infiniteImage,infiniteImage.width-offsetLeft,0);
setTimeout(function() {
requestAnimFrame(pan);
}, 1000 / fps);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=500 height=143></canvas><br>
</body>
</html>
You can achieve this quite easily with the html5 canvas.
Look at the drawImage specification here :
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#drawing-images
drawImage comes in 3 flavors, the first being a simple copy of the image, the second allowing to scale the image, and the third is the one you seek, it allows to perform clipping in a single call.
What you have to do :
- have a counter that will move from zero to the width of your image, then loop to zero again.
- each frame, draw the maximum of the image that you can on the canvas.
- If there is still some part of the canvas not drawn, draw again the image starting from zero to fill the canvas.
i made a fiddle, the only part that matters is in the animate function (other things are tools i often use in my fiddles).
(Rq : I assumed in this example that two images would be enough to fill the canvas.)
http://jsfiddle.net/gamealchemist/5VJhp/
var startx = Math.round(startPos);
var clippedWidth = Math.min(landscape.width - startx, canvasWidth);
// fill left part of canvas with (clipped) image.
ctx.drawImage(landscape, startx, 0, clippedWidth, landscape.height,
0, 0, clippedWidth, landscape.height);
if (clippedWidth < canvasWidth) {
// if we do not fill the canvas
var remaining = canvasWidth - clippedWidth;
ctx.drawImage(landscape, 0, 0, remaining, landscape.height,
clippedWidth, 0, remaining, landscape.height);
}
// have the start position move and loop
startPos += dt * rotSpeed;
startPos %= landscape.width;
To answer my own question: I found a way to achieve this effect with EaselJS. The great benefit of this method is that you don't have to check for the position of your bitmap. It will scroll infinitely - without ever resetting the position to 0.
The trick is to fill a shape with a bitmapfill. You can set a bitmapfill to repeat infinitely. Then you use a Matrix2D to decide the offset of the bitmapfill. Since it repeats automatically, this will generate a scrolling effect.
function.createRoad() {
// road has a matrix, shape and image
_m = new createjs.Matrix2D();
// this gets the image from the preloader - but this can be any image
_r = queue.getResult("road");
// this creates a shape that will hold the repeating bitmap
_roadshape = new createjs.Shape();
// put the shape on the canvas
addChild(_roadshape);
}
//
// the draw code gets repeatedly called, for example by requestanimationframe
//
function.drawRoad() {
// var _speed = 4;
_m.translate(-_speed, 0);
_roadshape.graphics.clear().beginBitmapFill(_r, "repeat", _m).rect(0, 0, 900, 400);
}