I'm trying to make this code work with more than one id and i can't make it work.
I have tried with querySelectorAll, but with not succes.
I also read this article, but none of the options worked for me
Can anyone help me?
This is the code:
<script>
function Scroller(options) {
this.svg = options.el;
//Animation will end when the end is at which point of othe page. .9 is at about 90% down the page/
// .1 is 10% from the top of the page. Default is middle of the page.
this.animationBounds = {};
this.animationBounds.top = options.startPoint || .5;
this.animationBounds.bottom = options.endPoint || .5;
this.animationBounds.containerBounds = this.svg.getBoundingClientRect();
this.start = this.getPagePosition('top');
this.end = this.getPagePosition('bottom');
this.svgLength = this.svg.getTotalLength();
this.svg.style.strokeDasharray = this.svgLength;
this.animateLine();
window.addEventListener('scroll', this.animateLine.bind(this));
}
Scroller.prototype.getPagePosition = function (position) {
//These positions are all relative to the current window. So they top of the page will be negative and thus need to be
//subtracted to get a positive number
var distanceFromPageTop = document.body.getBoundingClientRect().top;
var divPosition = this.animationBounds.containerBounds[position];
var startPointInCurrentWindow = window.innerHeight * this.animationBounds[position];
return divPosition - distanceFromPageTop - startPointInCurrentWindow;
};
Scroller.prototype.animateLine = function () {
this.currentVisiblePosition = window.pageYOffset;
if (this.currentVisiblePosition < this.start) {
this.svg.style.strokeDashoffset = this.svgLength;
}
if (this.currentVisiblePosition > this.end) {
this.svg.style.strokeDashoffset = '0px';
}
if (this.currentVisiblePosition > this.start && this.currentVisiblePosition < this.end) {
this.svg.style.strokeDashoffset = this.distanceRemaining() * this.pixelsPerVerticalScroll() + 'px';
}
};
Scroller.prototype.distanceRemaining = function () {
return this.end - this.currentVisiblePosition;
};
Scroller.prototype.pixelsPerVerticalScroll = function () {
this.verticalDistance = this.end - this.start;
return this.svgLength / this.verticalDistance;
};
new Scroller({
'el': **document.getElementById('line')**,
'startPoint': .8,
'endPoint': .5
});
</script>
Loop over all the elements matching the selector.
var lines = document.querySelectorAll("#line, #line1, #line2");
for (var i = 0; i < lines.length; i++) {
new Scroller({
el: lines[i],
startPoint: .8,
endPoint: .5
});
}
Related
I would like to add multiple photos from the Array in this code to the elements, but it adds just one photo from the Array to the first Element.
I tried adding for loop, but I dont know where to start and where to end the loop. Could you please take a look to the code using the link (codepen)?
thank you
let zoomLevel = 1;
const images = [
{
thumb: 'http://localhost:8080/links/works/Print/001.webp',
hires: 'http://localhost:8080/links/works/Print/001.webp'
},
{
thumb: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp'
}
]
// set to random image
let img = images[Math.floor(Math.random() * images.length)];
image.getElementsByTagName('a')[0].setAttribute('href', img.hires);
image.getElementsByTagName('img')[0].setAttribute('src', img.thumb);
const preloadImage = url => {
let img = new Image();
img.src = url;
}
preloadImage(img.hires);
const enterImage = function(e) {
zoom.classList.add('show', 'loading');
clearTimeout(clearSrc);
let posX, posY, touch = false;
if (e.touches) {
posX = e.touches[0].clientX;
posY = e.touches[0].clientY;
touch = true;
} else {
posX = e.clientX;
posY = e.clientY;
}
You can check this better using Codepen HERE.
const image = document.querySelectorAll('.image');
/* Store the number of all elements with css class 'image' */
let imageElementsCount = image.length;
for (index = 0; index < imageElementsCount; index++)
{
let arrayElementPos = Math.floor(Math.random() * images.length);
/* Receive the requested element from array with image objects */
let imageObject = images[arrayElementPos];
preloadImage(imageObject.hires);
/* Assign received image properties to your html element */
image[index].getElementsByTagName('a')[0].setAttribute('href', imageObject.hires);
image[index].getElementsByTagName('img')[0].setAttribute('src', imageObject.thumb);
image[index].addEventListener('mouseover', enterImage);
image[index].addEventListener('touchstart', enterImage);
image[index].addEventListener('mouseout', leaveImage);
image[index].addEventListener('touchend', leaveImage);
image[index].addEventListener('mousemove', move);
image[index].addEventListener('touchmove', move);
image[index].addEventListener('wheel', e =>
{
e.preventDefault();
e.deltaY > 0 ? zoomLevel-- : zoomLevel++;
if (zoomLevel < 1) zoomLevel = 1;
if (zoomLevel > 5) zoomLevel = 5;
console.log(`zoom level: ${zoomLevel}`);
zoom.style.transform = `scale(${zoomLevel})`;
});
}
The loop is working until all founded divs got an assignment.
ToDos:
Remove in line
const image = document.querySelectorAll('.image')[0];
the [0].
Next step: Take a look into the body of for loop. Remove your lines of code in your original code
Thank you #Reporter, but I've allready done this editing my code for two days. :)
const zoo = document.querySelectorAll('.zoom');
const zooImg = document.querySelectorAll('.zoom-image');
const pic = document.querySelectorAll(".image");
let clearSrc;
let zoomLevel = 1;
const digiImgs = [{
thumb: 'https://tasvir-graphic.de/links/works/digital/MuZe.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/MuZe.webp'
},
{
thumb: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp'
},
{
thumb: 'https://tasvir-graphic.de/links/works/digital/takeCare.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/takeCare.webp'
},
]
// set to random image
for (var i = 0; i < pic.length; i++) {
let img = digiImgs[i];
pic[i].getElementsByTagName('a')[0].setAttribute('href', img.hires);
pic[i].getElementsByTagName('img')[0].setAttribute('src', img.thumb);
const preloadImage = url => {
let img = new Image();
img.src = url;
}
preloadImage(img.hires);
const enterImage = function (e) {
var zoo = this.parentNode.childNodes[3];
zoo.classList.add('show', 'loading');
clearTimeout(clearSrc);
let posX, posY, touch = false;
if (e.touches) {
posX = e.touches[0].clientX;
posY = e.touches[0].clientY;
touch = true;
} else {
posX = e.clientX;
posY = e.clientY;
}
touch
?
zoo.style.top = `${posY - zoo.offsetHeight / 1.25}px` :
zoo.style.top = `${posY - zoo.offsetHeight / 2}px`;
zoo.style.left = `${posX - zoo.offsetWidth / 2}px`;
let originalImage = this.getElementsByTagName('a')[0].getAttribute('href');
var zoImg = this.parentNode.childNodes[3].childNodes[1];
zoImg.setAttribute('src', originalImage);
// remove the loading class
zoImg.onload = function () {
setTimeout(() => {
zoo.classList.remove('loading');
}, 500);
}
}
const leaveImage = function () {
// remove scaling to prevent non-transition
var zoImg = this.parentNode.childNodes[3].childNodes[1];
var zoo = this.parentNode.childNodes[3];
zoo.style.transform = null;
zoomLevel = 1;
zoo.classList.remove('show');
clearSrc = setTimeout(() => {
zoImg.setAttribute('src', '');
}, 250);
}
const move = function (e) {
e.preventDefault();
var zoImg = this.parentNode.childNodes[3].childNodes[1];
var zoo = this.parentNode.childNodes[3];
let posX, posY, touch = false;
if (e.touches) {
posX = e.touches[0].clientX;
posY = e.touches[0].clientY;
touch = true;
} else {
posX = e.clientX;
posY = e.clientY;
}
// move the zoom a little bit up on mobile (because of your fat fingers :<)
touch ?
zoo.style.top = `${posY - zoo.offsetHeight / 1.25}px` :
zoo.style.top = `${posY - zoo.offsetHeight / 2}px`;
zoo.style.left = `${posX - zoo.offsetWidth / 2}px`;
let percX = (posX - this.offsetLeft) / this.offsetWidth,
percY = (posY - this.offsetTop) / this.offsetHeight;
let zoomLeft = -percX * zoImg.offsetWidth + (zoo.offsetWidth / 2),
zoomTop = -percY * zoImg.offsetHeight + (zoo.offsetHeight / 2);
zoImg.style.left = `${zoomLeft}px`;
zoImg.style.top = `${zoomTop}px`;
}
pic[i].addEventListener('mouseover', enterImage);
pic[i].addEventListener('touchstart', enterImage);
pic[i].addEventListener('mouseout', leaveImage);
pic[i].addEventListener('touchend', leaveImage);
pic[i].addEventListener('mousemove', move);
pic[i].addEventListener('touchmove', move);
pic[i].addEventListener('wheel', e => {
var zoo = e.target.parentNode.parentNode.parentNode.childNodes[3];
console.log(zoo);
e.preventDefault();
e.deltaY > 0 ? zoomLevel-- : zoomLevel++;
if (zoomLevel < 1) zoomLevel = 1;
if (zoomLevel > 3) zoomLevel = 3;
console.log(`zoom level: ${zoomLevel}`);
zoo.style.transform = `scale(${zoomLevel})`;
});
}
but there is a problem with the touch. When I touch using mobile, the photo works like a link and opens the photo. How to use preventDefault Touch?
This is not a duplicate of the other question.
I found this talking about rotation about the center using XML, tried to implement the same using vanilla JavaScript like rotate(45, 60, 60) but did not work with me.
The approach worked with me is the one in the snippet below, but found the rect not rotating exactly around its center, and it is moving little bit, the rect should start rotating upon the first click, and should stop at the second click, which is going fine with me.
Any idea, why the item is moving, and how can I fix it.
var NS="http://www.w3.org/2000/svg";
var SVG=function(el){
return document.createElementNS(NS,el);
}
var svg = SVG("svg");
svg.width='100%';
svg.height='100%';
document.body.appendChild(svg);
class myRect {
constructor(x,y,h,w,fill) {
this.SVGObj= SVG('rect'); // document.createElementNS(NS,"rect");
self = this.SVGObj;
self.x.baseVal.value=x;
self.y.baseVal.value=y;
self.width.baseVal.value=w;
self.height.baseVal.value=h;
self.style.fill=fill;
self.onclick="click(evt)";
self.addEventListener("click",this,false);
}
}
Object.defineProperty(myRect.prototype, "node", {
get: function(){ return this.SVGObj;}
});
Object.defineProperty(myRect.prototype, "CenterPoint", {
get: function(){
var self = this.SVGObj;
self.bbox = self.getBoundingClientRect(); // returned only after the item is drawn
self.Pc = {
x: self.bbox.left + self.bbox.width/2,
y: self.bbox.top + self.bbox.height/2
};
return self.Pc;
}
});
myRect.prototype.handleEvent= function(evt){
self = evt.target; // this returns the `rect` element
this.cntr = this.CenterPoint; // backup the origional center point Pc
this.r =5;
switch (evt.type){
case "click":
if (typeof self.moving == 'undefined' || self.moving == false) self.moving = true;
else self.moving = false;
if(self.moving == true){
self.move = setInterval(()=>this.animate(),100);
}
else{
clearInterval(self.move);
}
break;
default:
break;
}
}
myRect.prototype.step = function(x,y) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(x,y));
}
myRect.prototype.rotate = function(r) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(r));
}
myRect.prototype.animate = function() {
self = this.SVGObj;
self.transform.baseVal.appendItem(this.step(this.cntr.x,this.cntr.y));
self.transform.baseVal.appendItem(this.rotate(this.r));
self.transform.baseVal.appendItem(this.step(-this.cntr.x,-this.cntr.y));
};
for (var i = 0; i < 10; i++) {
var x = Math.random() * 100,
y = Math.random() * 300;
var r= new myRect(x,y,10,10,'#'+Math.round(0xffffff * Math.random()).toString(16));
svg.appendChild(r.node);
}
UPDATE
I found the issue to be calculating the center point of the rect using the self.getBoundingClientRect() there is always 4px extra in each side, which means 8px extra in the width and 8px extra in the height, as well as both x and y are shifted by 4 px, I found this talking about the same, but neither setting self.setAttribute("display", "block"); or self.style.display = "block"; worked with me.
So, now I've one of 2 options, either:
Find a solution of the extra 4px in each side (i.e. 4px shifting of both x and y, and total 8px extra in both width and height),
or calculating the mid-point using:
self.Pc = {
x: self.x.baseVal.value + self.width.baseVal.value/2,
y: self.y.baseVal.value + self.height.baseVal.value/2
};
The second option (the other way of calculating the mid-point worked fine with me, as it is rect but if other shape is used, it is not the same way, I'll look for universal way to find the mid-point whatever the object is, i.e. looking for the first option, which is solving the self.getBoundingClientRect() issue.
Here we go…
FIDDLE
Some code for documentation here:
let SVG = ((root) => {
let ns = root.getAttribute('xmlns');
return {
e (tag) {
return document.createElementNS(ns, tag);
},
add (e) {
return root.appendChild(e)
},
matrix () {
return root.createSVGMatrix();
},
transform () {
return root.createSVGTransformFromMatrix(this.matrix());
}
}
})(document.querySelector('svg.stage'));
class Rectangle {
constructor (x,y,w,h) {
this.node = SVG.add(SVG.e('rect'));
this.node.x.baseVal.value = x;
this.node.y.baseVal.value = y;
this.node.width.baseVal.value = w;
this.node.height.baseVal.value = h;
this.node.transform.baseVal.initialize(SVG.transform());
}
rotate (gamma, x, y) {
let t = this.node.transform.baseVal.getItem(0),
m1 = SVG.matrix().translate(-x, -y),
m2 = SVG.matrix().rotate(gamma),
m3 = SVG.matrix().translate(x, y),
mtr = t.matrix.multiply(m3).multiply(m2).multiply(m1);
this.node.transform.baseVal.getItem(0).setMatrix(mtr);
}
}
Thanks #Philipp,
Solving catching the SVG center can be done, by either of the following ways:
Using .getBoundingClientRect() and adjusting the dimentions considering 4px are extra in each side, so the resulted numbers to be adjusted as:
BoxC = self.getBoundingClientRect();
Pc = {
x: (BoxC.left - 4) + (BoxC.width - 8)/2,
y: (BoxC.top - 4) + (BoxC.height - 8)/2
};
or by:
Catching the .(x/y).baseVal.value as:
Pc = {
x: self.x.baseVal.value + self.width.baseVal.value/2,
y: self.y.baseVal.value + self.height.baseVal.value/2
};
Below a full running code:
let ns="http://www.w3.org/2000/svg";
var root = document.createElementNS(ns, "svg");
root.style.width='100%';
root.style.height='100%';
root.style.backgroundColor = 'green';
document.body.appendChild(root);
//let SVG = function() {}; // let SVG = new Object(); //let SVG = {};
class SVG {};
SVG.matrix = (()=> { return root.createSVGMatrix(); });
SVG.transform = (()=> { return root.createSVGTransformFromMatrix(SVG.matrix()); });
SVG.translate = ((x,y)=> { return SVG.matrix().translate(x,y) });
SVG.rotate = ((r)=> { return SVG.matrix().rotate(r); });
class Rectangle {
constructor (x,y,w,h,fill) {
this.node = document.createElementNS(ns, 'rect');
self = this.node;
self.x.baseVal.value = x;
self.y.baseVal.value = y;
self.width.baseVal.value = w;
self.height.baseVal.value = h;
self.style.fill=fill;
self.transform.baseVal.initialize(SVG.transform()); // to generate transform list
this.transform = self.transform.baseVal.getItem(0), // to be able to read the matrix
this.node.addEventListener("click",this,false);
}
}
Object.defineProperty(Rectangle.prototype, "draw", {
get: function(){ return this.node;}
});
Object.defineProperty(Rectangle.prototype, "CenterPoint", {
get: function(){
var self = this.node;
self.bbox = self.getBoundingClientRect(); // There is 4px shift in each side
self.bboxC = {
x: (self.bbox.left - 4) + (self.bbox.width - 8)/2,
y: (self.bbox.top - 4) + (self.bbox.height - 8)/2
};
// another option is:
self.Pc = {
x: self.x.baseVal.value + self.width.baseVal.value/2,
y: self.y.baseVal.value + self.height.baseVal.value/2
};
return self.bboxC;
// return self.Pc; // will give same output of bboxC
}
});
Rectangle.prototype.animate = function () {
let move01 = SVG.translate(this.CenterPoint.x,this.CenterPoint.y),
move02 = SVG.rotate(10),
move03 = SVG.translate(-this.CenterPoint.x,-this.CenterPoint.y);
movement = this.transform.matrix.multiply(move01).multiply(move02).multiply(move03);
this.transform.setMatrix(movement);
}
Rectangle.prototype.handleEvent= function(evt){
self = evt.target; // this returns the `rect` element
switch (evt.type){
case "click":
if (typeof self.moving == 'undefined' || self.moving == false) self.moving = true;
else self.moving = false;
if(self.moving == true){
self.move = setInterval(()=>this.animate(),100);
}
else{
clearInterval(self.move);
}
break;
default:
break;
}
}
for (var i = 0; i < 10; i++) {
var x = Math.random() * 100,
y = Math.random() * 300;
var r= new Rectangle(x,y,10,10,'#'+Math.round(0xffffff * Math.random()).toString(16));
root.appendChild(r.draw);
}
I'm trying to create the following animation:
- when the page loads, I load the 2 components of my logo and draw them onto the canvas
- after clicking the animate button, both images should smoothly move up to the top of the page and shrink
However, it seems that the drawImage() only takes place somewhere after the entire animation has been completed even though I can see it being executed at the right time in the console. Commenting out the clearRect function also shows me that every frame is actually drawn onto the screen, but sadly they all appear together after the animation should be completed.
I'm pretty new to canvases and relevant tutorials on such animations are scarce and don't get me much further.
Here's the relevant script:
<script type="text/javascript">
var logoCanvas;
var logoContext;
var direction = 1;
var logoAnimationDuration = 2000; //millisec
var frameSpeed = 30/1000; //frames per second
var logoImageRatio = 0;
var logoTextRatio = 0;
var initialLogoImageHeight = 0;
var initialLogoTextHeight = 0;
var initialLogoImageTop = 0;
var initialLogoImageLeft = 0;
var initialLogoTextTop = 0;
var initialLogoTextLeft = 0;
var newLogoImageLeft = 0;
var logoImageWidth = 0;
var logoImageHeight = 0;
var logoTextWidth = 0;
var logoTextHeight = 0;
var logoImageTop = 0;
var logoImageLeft = 0;
var logoTextTop = 0;
var logoTextLeft = 0;
var logoImageLoaded = false;
var logoTextLoaded = false;
var logoImage = new Image();
var logoText = new Image();
function AnimateFrontPageCanvas() {
if(logoCanvas.height <= $('#Page_0').height()) {
logoCanvas.height = $('#Page_0').height();
return;
}
if(logoImageHeight <= $('#Page_0').height() - 4) {
return;
}
if(logoImageTop <= 2) {
return;
}
//logoCanvas.height = parseFloat(logoCanvas.height) - (parseFloat(window.innerHeight) - parseFloat($('#Page_0').height())) * 1/frameSpeed * 1/logoAnimationDuration;
logoContext.clearRect(0, 0, logoCanvas.width, logoCanvas.height);
AnimateLogo();
setTimeout(AnimateFrontPageCanvas(), 1/frameSpeed);
}
function AnimateLogo() {
AnimateLogoImage();
AnimateLogoText();
}
function AnimateLogoImage() {
logoImageHeight = logoImageHeight - (initialLogoImageHeight - $('#Page_0').height() + 4) * (1/frameSpeed) * (1/logoAnimationDuration);
logoImageWidth = logoImageHeight*logoImageRatio;
logoImageTop = logoImageTop - (initialLogoImageTop - 2)*(1/frameSpeed) * (1/logoAnimationDuration);
logoImageLeft = logoImageLeft - (initialLogoImageLeft - newLogoImageLeft) * (1/frameSpeed) * (1/logoAnimationDuration);
logoContext.drawImage(logoImage, logoImageLeft, logoImageTop, logoImageWidth, logoImageHeight);
var time = new Date();
time = time.getTime();
console.log('logoimage drawn: ' + time);
}
function AnimateLogoText() {
logoTextHeight = logoTextHeight - (initialLogoTextHeight - $('#Page_0').height() + 4) * (1/frameSpeed) * (1/logoAnimationDuration);
logoTextWidth = logoTextHeight*logoTextRatio;
logoTextTop = logoTextTop - (initialLogoTextTop - 2) * (1/frameSpeed)*(1/logoAnimationDuration);
logoTextLeft = logoTextLeft - (initialLogoTextLeft - newLogoTextLeft)*(1/frameSpeed)*(1/logoAnimationDuration);
logoContext.drawImage(logoText, logoTextLeft, logoTextTop, logoTextWidth, logoTextHeight);
var time = new Date();
time = time.getTime();
console.log('logotext drawn: ' + time);
}
function InitiateFrontPageCanvas() {
logoImage.onload = function() {
logoImageLoaded = true;
AfterImagesLoadedActions();
}
logoImage.src = '/site_mats/images/logo_image.png';
logoText.onload = function() {
logoTextLoaded = true;
AfterImagesLoadedActions();
}
logoText.src = '/site_mats/images/logo_text.png';
}
function AfterImagesLoadedActions() {
if(logoImageLoaded && logoTextLoaded) {
logoImageRatio = logoImage.width/logoImage.height;
logoTextRatio = logoText.width/logoText.height;
if(logoImage.width > 0.8*logoCanvas.width) {
logoImage.width = 0.8*logoCanvas.width;
logoImage.height = logoImage.width/logoImageRatio;
}
if(logoText.width > 0.8*logoCanvas.width) {
logoText.width = 0.8*logoCanvas.width;
logoText.height = logoText.width/logoTextRatio;
}
if(parseFloat(logoImage.height) + parseFloat(logoText.height) > 0.66*logoCanvas.height) {
var x = (0.66*logoCanvas.height)/(parseFloat(logoImage.height) + parseFloat(logoText.height));
logoImage.height = x*logoImage.height;
logoImage.width = logoImage.height*logoImageRatio;
logoText.height = x*logoText.height;
logoText.width = logoText.height*logoTextRatio;
}
initialLogoImageHeight = logoImage.height;
initialLogoTextHeight = logoText.height;
logoImageHeight = logoImage.height;
logoImageWidth = logoImage.width;
logoTextHeight = logoText.height;
logoTextWidth = logoText.width;
initialLogoImageTop = parseFloat((logoCanvas.height-(parseFloat(logoImageHeight)+parseFloat(logoTextHeight)))/2);
initialLogoImageLeft = parseFloat((logoCanvas.width-logoImageWidth)/2);
initialLogoTextTop = parseFloat(((logoCanvas.height-(parseFloat(logoImageHeight)+parseFloat(logoTextHeight)))/2)+parseFloat(logoImageHeight));
initialLogoTextLeft = parseFloat((logoCanvas.width-logoImageWidth)/2);
logoImageTop = initialLogoImageTop;
logoImageLeft = initialLogoImageLeft;
logoTextTop = initialLogoTextTop;
logoTextLeft = initialLogoTextLeft;
newLogoImageLeft = parseFloat(2);
newLogoTextLeft = parseFloat((logoCanvas.width-$('#Page_0').height()*logoTextRatio)/2);;
logoContext.drawImage(logoText, logoTextLeft, logoTextTop, logoTextWidth, logoTextHeight);
logoContext.drawImage(logoImage, logoImageLeft, logoImageTop, logoImageWidth, logoImageHeight);
//remove onload from image objects
logoImage.onload = null;
logoText.onload = null;
}
}
function SetFrontPageCanvas() {
logoCanvas = document.getElementById('LogoCanvas');
logoContext = logoCanvas.getContext('2d');
logoCanvas.width = window.innerWidth;
logoCanvas.height = window.innerHeight;
logoContext.clearRect(0, 0, logoCanvas.width, logoCanvas.height);
InitiateFrontPageCanvas();
}
window.onload = function() {
SetFrontPageCanvas();
};
$(document).ready(function() {
windowWidth = $(window).width();
windowHeight = $(window).height();
});
$(window).resize(function() {
//alert($(window).width() + ' - ' + $(window).height());
logoImageLoaded = false;
logoTextLoaded = false;
SetFrontPageCanvas();
windowWidth = $(window).width();
windowHeight = $(window).height();
});
</script>
And here's the relevant html:
<canvas id="LogoCanvas" style="position:absolute;top:0;left:0;z-index:1;"></canvas>
<div id="Page_0" style="position:absolute;top:0;left:0;z-index:5;width:100%;height:90px;">
<div align="center" style="position:fixed;bottom:5px;z-index:100;width:100%;">
<input type="button" onClick="AnimateFrontPageCanvas()" value="Animate">
</div>
If desired, the code can also be seen in action here: http://villa-gloria-katouna.com/site_mats/animation.php
I need to create an animation of dropping box, which supposed to bounce 10 times when it reaches a certain Y point on canvas, each time twice lower that the previous. So I have the animation of the dropping box, but I can't make the bounce work. Here are the functions that I wrote:
function dropBox(y, width, height) {
var img_box = new Image();
img_box.src = 'images/gift_box_small.png';
var box_y_pos = y;
if(y==0)
box_y_pos = y-img_box.naturalHeight;
img_box.onload = function(){
ctx_overlay.save();
ctx_overlay.clearRect(0,0,width,height);
ctx_overlay.drawImage(img_box, (width/2)-(img_box.naturalWidth/2), box_y_pos);
ctx_overlay.restore();
}
box_y_pos += 3;
var box_bottom_position = box_y_pos - img_box.naturalHeight;
if(box_y_pos+img_box.naturalHeight<height-25)
var loopTimer = setTimeout(function() {dropBox(box_y_pos, width, height)},24);
else
bounceBox(img_box, box_y_pos, box_y_pos, (height/2)-(img_box.naturalHeight/2), "up");
}
function bounceBox(img, img_final_pos, y, midway_pos, direction){
var midway = midway_pos;
var direction = direction;
var img_y_pos = y;
img.onload = function(){
ctx_overlay.save();
ctx_overlay.clearRect(0,0,docWidth,docHeight);
ctx_overlay.drawImage(img, (docWidth/2)-(img.naturalWidth/2), img_y_pos);
ctx_overlay.restore();
}
for(var i = 0; i < 10; i++){
if(direction=="up"){
//going up
if(img_y_pos>midway_){
img_y_pos -= 3;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "up")},24);
} else {
img_y_pos += 3;
midway = Math.floor(midway /= 2);
if(midway%2>0)
midway += 1;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "down")},24);
}
} else {
//going down
if(img_y_pos < img_final_pos){
img_y_pos += 3;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "down")},24);
}
}
}
}
JSFiddle: http://jsfiddle.net/n2derqgw/3/
Why isn't it working and how can I make it work?
To avoid getting a headache, you've better handle the animation within a single function called with a setInterval.
And keep all animation-related data in one object.
So the code below does not exactly what you want, but should get you started :
http://jsfiddle.net/n2derqgw/4/
Setup :
var canvas_overlay, ctx_overlay, docWidth, docHeight;
var img_box = new Image();
img_box.src = 'http://corkeynet.com/test/images/gift_box_small.png';
var mustBeReadyCount = 2; // must load image and window
img_box.onload = launchWhenReady;
window.onload = launchWhenReady;
var animationStep = '';
var boxAnimationData = {
animationStep: '',
y: 0,
maxY: 0,
bounceCount: 6,
direction: -1,
bounceHeight: 0
};
function launchWhenReady() {
mustBeReadyCount--;
if (mustBeReadyCount) return;
docWidth = window.innerWidth;
docHeight = window.innerHeight;
canvas_overlay = document.getElementById('canvas_overlay');
ctx_overlay = canvas_overlay.getContext('2d');
resizeCanvas(docWidth, docHeight);
boxAnimationData.animationStep = 'falling';
boxAnimationData.bounceHeight = docHeight / 2 - img_box.height;
setInterval(animateBox, 30);
};
More interesting code is here :
function animateBox() {
if (boxAnimationData.animationStep == 'falling') dropBox();
else if (boxAnimationData.animationStep == 'bouncing') bounceBox();
}
function dropBox() {
ctx_overlay.clearRect(0, 0, docWidth, docHeight);
boxAnimationData.y += 3;
if (boxAnimationData.y + img_box.height > docHeight) {
boxAnimationData.animationStep = 'bouncing';
}
ctx_overlay.drawImage(img_box, (docWidth / 2) - (img_box.width / 2), boxAnimationData.y);
}
function bounceBox() {
ctx_overlay.clearRect(0, 0, docWidth, docHeight);
boxAnimationData.y += boxAnimationData.direction * 3;
if (boxAnimationData.y + img_box.height > docHeight) {
// reached floor ? swap direction
boxAnimationData.direction *= -1;
// and reduce jump height
boxAnimationData.bounceHeight *= 3 / 2;
boxAnimationData.bounceCount--;
if (!boxAnimationData.bounceCount) boxAnimationData.animationStep = '';
} else if (boxAnimationData.y < boxAnimationData.bounceHeight) {
boxAnimationData.direction *= -1;
}
ctx_overlay.drawImage(img_box, (docWidth / 2) - (img_box.width / 2), boxAnimationData.y);
}
Basically below is my script for a prototype which uses 128x128 tiles to draw a map on a canvas which user can drag to move around.
Script does work. However I have a few problems to be solved:
1. Poor performance and I can't figure out why.
2. I am missing a method to buffer the tiles before the actual drawing.
3. If you notice any other issues also that could help me to make things run more smoothly it would be fantastic.
Some explanations for the script:
variables
coordinates - Defines the actual images to be displayed. Image file names are type of '0_1.jpg', where 0 is Y and 1 is X.
mouse_position - As name says, is keeping record of mouse position.
position - This is a poorly named variable. It defines the position of the context drawn on canvas. This changes when user drags the view.
Any assistance would be appreciated greatly. Thank you.
var coordinates = [0, 0];
var mouse_position = [0, 0];
var position = [-128, -128];
var canvas = document.getElementById('map_canvas');
var context = canvas.getContext('2d');
var buffer = [];
var buffer_x = Math.floor(window.innerWidth/128)+4;
var buffer_y = Math.floor(window.innerHeight/128)+4;
var animation_frame_request = function() {
var a = window.requestAnimationFrame;
var b = window.webkitRequestAnimationFrame;
var c = window.mozRequestAnimationFrame;
var d = function(callback) {
window.setTimeout(callback, 1000/60);
}
return a || b || c || d;
}
var resizeCanvas = function() {
window.canvas.width = window.innerWidth;
window.canvas.height = window.innerHeight;
window.buffer_x = Math.floor(window.innerWidth/128)+4;
window.buffer_y = Math.floor(window.innerHeight/128)+4;
window.buffer = [];
for (row = 0; row < window.buffer_y; row++) {
x = [];
for (col = 0; col < window.buffer_x; col++) {
x.push(new Image());
}
window.buffer.push(x);
}
}
var render = function() {
animation_frame_request(render);
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
cy = window.coordinates[1]+row;
cx = window.coordinates[0]+col;
window.buffer[row][col].src = 'map/'+cy+'_'+cx+'.jpg';
}
}
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
window.context.drawImage(window.buffer[row][col],
window.position[0]+col*128,
window.position[1]+row*128, 128, 128);
}
}
}
var events = function() {
window.canvas.onmousemove = function(e) {
if (e['buttons'] == 1) {
window.position[0] += (e.clientX-window.mouse_position[0]);
window.position[1] += (e.clientY-window.mouse_position[1]);
if (window.position[0] >= 0) {
window.position[0] = -128;
window.coordinates[0] -= 1;
} else if (window.position[0] < -128) {
window.position[0] = 0;
window.coordinates[0] += 1;
}
if (window.position[1] >= 0) {
window.position[1] = -128;
window.coordinates[1] -= 1;
} else if (window.position[1] < -128) {
window.position[1] = 0;
window.coordinates[1] += 1;
}
render();
}
window.mouse_position[0] = e.clientX;
window.mouse_position[1] = e.clientY;
}
}
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('load', resizeCanvas, false);
window.addEventListener('mousemove', events, false);
resizeCanvas();
To get better performance you should avoid changing the src of img nodes and move them around instead.
A simple way to minimize the number of img nodes handled and modified (except for screen positioning) is to use an LRU (Least Recently Used) cache.
Basically you keep a cache of last say 100 image nodes (they must be enough to cover at least one screen) by using a dictionary mapping the src url to a node object and also keeping them all in a doubly-linked list.
When a tile is required you first check in the cache, and if it's already there just move it to the front of LRU list and move the img coordinates, otherwise create a new node and set the source or, if you already hit the cache limit, reuse the last node in the doubly-linked list instead. In code:
function setTile(x, y, src) {
var t = cache[src];
if (!t) {
if (cache_count == MAXCACHE) {
t = lru_last;
t.prev.next = null;
lru_last = t.prev;
t.prev = t.next = null;
delete cache[t.src]
t.src = src;
t.img.src = src;
cache[t.src] = t;
} else {
t = { prev: null,
next: null,
img: document.createElement("img") };
t.src = src;
t.img.src = src;
t.img.className = "tile";
scr.appendChild(t.img);
cache[t.src] = t;
cache_count += 1;
}
} else {
if (t.prev) t.prev.next = t.next; else lru_first = t.next;
if (t.next) t.next.prev = t.prev; else lru_last = t.prev;
}
t.prev = null; t.next = lru_first;
if (t.next) t.next.prev = t; else lru_last = t;
lru_first = t;
t.img.style.left = x + "px";
t.img.style.top = y + "px";
scr.appendChild(t.img);
}
I'm also always appending the requested tile to the container so that it goes in front of all other existing tiles; this way I don't need to remove old tiles and they're simply left behind.
To update the screen I just iterate over all the tiles I need and request them:
function setView(x0, y0) {
var w = scr.offsetWidth;
var h = scr.offsetHeight;
var iy0 = y0 >> 7;
var ix0 = x0 >> 7;
for (var y=iy0; y*128 < y0+h; y++) {
for (var x=ix0; x*128 < x0+w; x++) {
setTile(x*128-x0, y*128-y0, "tile_" + y + "_" + x + ".jpg");
}
}
}
most of the time the setTile request will just update the x and y coordinates of an existing img tag, without changing anything else. At the same time no more than MAXCACHE image nodes will be present on the screen.
You can see a full working example in
http://raksy.dyndns.org/tiles/tiles.html