Raphael js | how to animate with two images - javascript

i am making a image scroller for my website,and i have two pictures.
i want the user to click on the next button,the image to rotate 180 degrees over the course of 250 milliseconds,change pictures,and the rotate 180 degrees again into position over the course of 250 milliseconds.
would there be a way to do that?
back.node.onclick=function(){
kitaimage1.animate({
transform : "r180",
kitaimage1.hide();
testimg1.show();
testimg1.animate({ transform : "r180" });
},250);
}
is what i have so far,but it doesnt work.
thanks in advance,noam haber

That's absolutely possible, although there are a lot of ways you could structure it. Here is one of many possible solutions. I assume the presence of a Raphael paper object named paper throughout.
Location and Dimensions
var cx = 180, cy = 125; /* These are the coordinates of the center point of your image(s) inside the paper */
var dx = 320, dy = 240; /* dx and dy describe the dimensions of the desired image interface. */
Image Information
I'd assume you'd want to load this information dynamically via ajax (depending on how your gallery is structured) or be able to re-use it. This example uses an array of objects describing the url, width, and height of each item in the list. For instance...
var imageList =
[
{
url: '/tests/images/sunset1.jpg',
width: 1600,
height: 900,
},
{
url: '/tests/images/moonrise1.jpg',
width: 1024,
height: 768,
},
{
url: '/tests/images/cityscape.jpg',
width: 1920,
height: 1080,
},
{
url: '/tests/images/forest03.jpg',
width: 500,
height: 325,
},
];
The Code
Last but not least, run this code when your DOM is ready (or after your images have loaded, or whatever other event should trigger it). This just creates a function to do the transition and then calls it to get the first image into place.
var imageIndex = 0;
var rotationMultiplier = 1;
var currentImage = null;
function rotateTransitionNext()
{
rotationMultiplier *= -1;
imageIndex++;
if ( imageIndex >= imageList.length )
imageIndex = 0;
if ( currentImage )
{
currentImage.animate( { transform: ["...r", 180 * rotationMultiplier, cx, cy], opacity: 0.0 }, 1000, '<>', function()
{
this.remove();
} );
}
var newImage = paper.image( imageList[imageIndex].url, cx - dx / 2, cy - dy / 2, dx, dy )
.attr( { transform: [ "r", -180 * rotationMultiplier, cx, cy], opacity: 0.0 } )
.animate( { transform: [ "...r", 180 * rotationMultiplier, cx, cy ], opacity: 1.0 }, 1000, '<>', function()
{
currentImage = this;
this.click( rotateTransitionNext );
} );
}
rotateTransitionNext();
Loose Ends
As a rough starting point, there are a few things that should be done to wrap this up:
For goodness' sake, wrap this stuff up in its own namespace to avoid cluttering the global scope with detritus (my bad)
Preserve aspect ratios of different images. Right now, all images are being forced to conform to the globally defined dimensions (dx and dy)
Happy coding.
UPDATE: here is a staged example of this code at work. Just click on the image to cause it to rotate to the next.

Related

Phaser Javascript Spinning Wheel auto spin issue

I am having problem trying to make a spinning wheel spin automatically on page load and also display multiple wheels on the same page. The spinner is based on Phaser. I am not a JavaScript nor Phaser programmer so your assistance is appreciated. This is probably something simple to do.
Here is the game.js code for the spinning wheel. (The complete spinning wheel script can be downloaded here)
// the game itself
var game;
var gameOptions = {
// slices (prizes) placed in the wheel
slices: 8,
// prize names, starting from 12 o'clock going clockwise
slicePrizes: ["A KEY!!!", "50 STARS", "500 STARS", "BAD LUCK!!!", "200 STARS", "100 STARS", "150 STARS", "BAD LUCK!!!"],
// wheel rotation duration, in milliseconds
rotationTime: 3000
}
// once the window loads...
window.onload = function() {
// game configuration object
var gameConfig = {
// render type
type: Phaser.CANVAS,
// game width, in pixels
width: 550,
// game height, in pixels
height: 550,
// game background color
backgroundColor: 0x880044,
// scenes used by the game
scene: [playGame]
};
// game constructor
game = new Phaser.Game(gameConfig);
// pure javascript to give focus to the page/frame and scale the game
window.focus()
resize();
window.addEventListener("resize", resize, false);
}
// PlayGame scene
class playGame extends Phaser.Scene{
// constructor
constructor(){
super("PlayGame");
}
// method to be executed when the scene preloads
preload(){
// loading assets
this.load.image("wheel", "wheel.png");
this.load.image("pin", "pin.png");
}
// method to be executed once the scene has been created
create(){
// adding the wheel in the middle of the canvas
this.wheel = this.add.sprite(game.config.width / 2, game.config.height / 2, "wheel");
// adding the pin in the middle of the canvas
this.pin = this.add.sprite(game.config.width / 2, game.config.height / 2, "pin");
// adding the text field
this.prizeText = this.add.text(game.config.width / 2, game.config.height - 20, "Spin the wheel", {
font: "bold 32px Arial",
align: "center",
color: "white"
});
// center the text
this.prizeText.setOrigin(0.5);
// the game has just started = we can spin the wheel
this.canSpin = true;
// waiting for your input, then calling "spinWheel" function
this.input.on("pointerdown", this.spinWheel, this);
}
// function to spin the wheel
spinWheel(){
// can we spin the wheel?
if(this.canSpin){
// resetting text field
this.prizeText.setText("");
// the wheel will spin round from 2 to 4 times. This is just coreography
var rounds = Phaser.Math.Between(2, 4);
// then will rotate by a random number from 0 to 360 degrees. This is the actual spin
var degrees = Phaser.Math.Between(0, 360);
// before the wheel ends spinning, we already know the prize according to "degrees" rotation and the number of slices
var prize = gameOptions.slices - 1 - Math.floor(degrees / (360 / gameOptions.slices));
// now the wheel cannot spin because it's already spinning
this.canSpin = false;
// animation tweeen for the spin: duration 3s, will rotate by (360 * rounds + degrees) degrees
// the quadratic easing will simulate friction
this.tweens.add({
// adding the wheel to tween targets
targets: [this.wheel],
// angle destination
angle: 360 * rounds + degrees,
// tween duration
duration: gameOptions.rotationTime,
// tween easing
ease: "Cubic.easeOut",
// callback scope
callbackScope: this,
// function to be executed once the tween has been completed
onComplete: function(tween){
// displaying prize text
this.prizeText.setText(gameOptions.slicePrizes[prize]);
// player can spin again
this.canSpin = true;
}
});
}
}
}
// pure javascript to scale the game
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else{
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
Does anyone know how to make it so that
it will start spinning as soon as the page is loaded.
place more than one wheel on a page. When I call the JavaScript twice, it does not display two wheels. The purpose of this exercise is to feed the wheel with two different "degrees" so that it can display the two results. The alternative will be to spin the wheel twice. After the first spin, display the first result and spin again to display the second result. But this is probably harder to do than simply display two wheels.
Thanks in advance.
For the first part of your question , '...spinning as soon as page is loaded..', the answer is:
just add the code line this.spinWheel(); at the end of the create function, this will start it right away.
For the second part:
just add in the gameConfig - object the property parent, with an id of an html-tag
create a game instance (no action needed)
than copy and past the whole gameConfig
change the parent, to a different tag-id
create a second game instance. ( add the line game = new Phaser.Game(gameConfig);)
Here a short Demo showcasing, these two fixes:
(depending on your exact goal, and what you want to achieve, there might be better ways to solve this)
FYI:
Images are not loaded, because they won't work here, but one can see how the auto spinning would work.
I remove code that was not so relevant for the demo, so that it can be understund more easy.
document.body.style = 'margin:0;';
var gameOptions = {
slices: 8,
slicePrizes: ["A KEY!!!", "50 STARS", "500 STARS", "BAD LUCK!!!", "200 STARS", "100 STARS", "150 STARS", "BAD LUCK!!!"],
rotationTime: 3000
}
class playGame extends Phaser.Scene{
constructor(){
super("PlayGame");
}
preload(){
//this.load.image("wheel", "wheel.png");
//this.load.image("pin", "pin.png");
}
create(){
this.wheel = this.add.sprite(game.config.width / 2, game.config.height / 2, "wheel");
this.pin = this.add.sprite(game.config.width / 2, game.config.height / 2, "pin");
this.prizeText = this.add.text(game.config.width / 2, game.config.height - 20, "Spin the wheel", {
font: "bold 32px Arial",
align: "center",
color: "white"
});
this.prizeText.setOrigin(0.5);
this.canSpin = true;
this.input.on("pointerdown", this.spinWheel, this);
this.spinWheel();
}
spinWheel(){
if(this.canSpin){
this.prizeText.setText("");
var rounds = Phaser.Math.Between(2, 4);
var degrees = Phaser.Math.Between(0, 360);
var prize = gameOptions.slices - 1 - Math.floor(degrees / (360 / gameOptions.slices));
this.canSpin = false;
this.tweens.add({
targets: [this.wheel],
angle: 360 * rounds + degrees,
duration: gameOptions.rotationTime,
ease: "Cubic.easeOut",
callbackScope: this,
onComplete: function(tween){
this.prizeText.setText(gameOptions.slicePrizes[prize]);
this.canSpin = true;
}
});
}
}
}
var gameConfig = {
type: Phaser.CANVAS,
parent: 'wheel1',
width: 200,
height: 200,
backgroundColor: 0x880044,
scene: [playGame]
};
var game = new Phaser.Game(gameConfig);
gameConfig = {
type: Phaser.CANVAS,
parent: 'wheel2',
width: 200,
height: 200,
// I altered also the background color to show the difference
backgroundColor: 0x0ff044,
scene: [playGame]
};
game = new Phaser.Game(gameConfig);
#wheel1, #wheel2, canvas{
display:inline-block;
padding:0;
margin:0;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
<div id="wheel1"></div><div id="wheel2"></div>
Updated - Update:
The main problem is that I made a mistake, the property is not parentElement it is parent. I tried to fix this in following demo.
// the game itself
var game;
var gameOptions = {
// slices (prizes) placed in the wheel
slices: 8,
// prize names, starting from 12 o'clock going clockwise
// this array has to contain atleast 8 value
slicePrizes: ["A KEY!!!", "50 STARS", "500 STARS", "BAD LUCK!!!", "200 STARS", "100 STARS", "150 STARS", "BAD LUCK!!!"],
// wheel rotation duration, in milliseconds
rotationTime: 3000
}
var gameConfig;
// once the window loads...
window.onload = function () {
gameConfig = {
type: Phaser.CANVAS,
parent: 'wheel1',
width: 200,
height: 200,
backgroundColor: 0x880044,
scene: [playGame]
};
var game = new Phaser.Game(gameConfig);
game.scene.start('PlayGame', { degrees: 60 });
gameConfig = {
type: Phaser.CANVAS,
parent: 'wheel2',
width: 200,
height: 200,
// I altered also the background color to show the difference
backgroundColor: 0x0ff044,
scene: [playGame]
};
var game2 = new Phaser.Game(gameConfig);
game2.scene.start('PlayGame', { degrees: 40 });
window.focus()
resize();
window.addEventListener("resize", resize, false);
}
// PlayGame scene
class playGame extends Phaser.Scene {
// constructor
constructor() {
super({ key: "PlayGame" });
}
// method to be executed when the scene preloads
preload() {
// loading assets
//this.load.image("wheel", "wheel.png");
//this.load.image("pin", "pin.png");
}
// method to be executed once the scene has been created
create(data) {
// adding the wheel in the middle of the canvas
this.wheel = this.add.sprite(gameConfig.width / 2, gameConfig.height / 2, "wheel");
// adding the pin in the middle of the canvas
this.pin = this.add.sprite(gameConfig.width / 2, gameConfig.height / 2, "pin");
// adding the text field
this.prizeText = this.add.text(gameConfig.width / 2, gameConfig.height - 20, "Spin the wheel", {
font: "bold 32px Arial",
align: "center",
color: "black"
});
// center the text
this.prizeText.setOrigin(0.5);
// the game has just started = we can spin the wheel
this.canSpin = true;
//this.input.on("pointerdown", this.spinWheel, this);
this.spinWheel(data.degrees);
}
// function to spin the wheel
spinWheel(degrees) {
// can we spin the wheel?
if (this.canSpin) {
// resetting text field
this.prizeText.setText("");
// the wheel will spin round from 2 to 4 times. This is just coreography
var rounds = Phaser.Math.Between(8, 10);
//var degrees = Phaser.Math.Between(0, 360);
var prize = gameOptions.slices - 1 - Math.floor(degrees / (360 / gameOptions.slices));
// now the wheel cannot spin because it's already spinning
this.canSpin = false;
// animation tweeen for the spin: duration 3s, will rotate by (360 * rounds + degrees) degrees
// the quadratic easing will simulate friction
this.tweens.add({
// adding the wheel to tween targets
targets: [this.wheel],
// angle destination
angle: 360 * rounds + degrees,
// tween duration
duration: gameOptions.rotationTime,
// tween easing
ease: "Cubic.easeOut",
// callback scope
callbackScope: this,
// function to be executed once the tween has been completed
onComplete: function (tween) {
// displaying prize text
this.prizeText.setText(gameOptions.slicePrizes[prize]);
// player can spin again
this.canSpin = true;
}
});
}
}
}
// pure javascript to scale the game
function resize() {
var canvas = document.querySelector("#wheels");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = gameConfig.width / gameConfig.height;
if (windowRatio < gameRatio) {
canvas.style.width = windowWidth + "px";
//canvas.style.height = (windowWidth / gameRatio) + "px";
}
else {
canvas.style.width = (windowHeight * gameRatio) + "px";
//canvas.style.height = windowHeight + "px";
}
}
body{
padding: 0px;
margin: 0px;
}
#wheels{
display: flex;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
align-items: stretch;
}
#wheel1, #wheel2, canvas{
flex-grow: 1;
margin: 0;
padding: 0;
}
canvas{
width: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
<div id="wheels">
<div id="wheel1"></div>
<div id="wheel2"></div>
</div>

How to increase quality on a PixiJS canvas?

I made horizontal scroll website with PIXI JS and it has a ripple effect.
it worked fine till now but the only issue is it's showing a very low quality when I open up the website and from what I figured out that PIXI JS renderer get width and height to 800 x 600 resolution.
any ideas how to change the quality ?
here's the PIXI JS code snippet:
// Set up the variables needed and loads the images to create the effect.
// Once the images are loaded the ‘setup’ function will be called.
const app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
resolution: 1,
antialias : true
});
document.body.appendChild(app.view);
app.stage.interactive = true;
var posX, displacementSprite, displacementFilter, bg, vx;
var container = new PIXI.Container();
app.stage.addChild(container);
PIXI.loader.add("depth.png").add("polygonexample.jpg").load(setup);
// In the ‘setup’ function the displacement sprite is created
// that will create the effect and this is added to a displacement filter.
// It’s then set to move its anchor point to the centre of the image and positioned on the screen.
function setup() {
posX = app.renderer.width / 2;
displacementSprite = new PIXI.Sprite(PIXI.loader.resources["depth.png"].texture);
displacementFilter = new PIXI.filters.DisplacementFilter(displacementSprite);
displacementSprite.anchor.set(0.5);
displacementSprite.x = app.renderer.width / 2;
displacementSprite.y = app.renderer.height / 2;
vx = displacementSprite.x;
// To finish off the ‘setup’ function, the displacement filter scale is set and the background positioned.
// Notice the scale is ‘0’ for the displacement, that’s because it will be set to a height as soon as the mouse moves.
app.stage.addChild(displacementSprite);
container.filters = [displacementFilter];
displacementFilter.scale.x = 0;
displacementFilter.scale.y = 0;
bg = new PIXI.Sprite(PIXI.loader.resources["polygonexample.jpg"].texture);
bg.width = app.renderer.width;
bg.height = app.renderer.height;
container.addChild(bg);
app.stage.on('mousemove', onPointerMove).on('touchmove', onPointerMove);
loop();
}
// grab the position of the mouse on the x-axis whenever the mouse moves.
function onPointerMove(eventData) {
posX = eventData.data.global.x;
}
// create a function that continually updates the screen. A velocity for the x-axis is worked out using the position of the mouse and the ripple.
function loop() {
requestAnimationFrame(loop);
vx += (posX - displacementSprite.x) * 0.045;
displacementSprite.x = vx;
var disp = Math.floor(posX - displacementSprite.x);
if (disp < 0) disp = -disp;
var fs = map(disp, 0, 500, 0, 120);
disp = map(disp, 0, 500, 0.1, 0.6);
displacementSprite.scale.x = disp;
displacementFilter.scale.x = fs;
}
// Finally, the map function is declared that maps value ranges to new values.
map = function(n, start1, stop1, start2, stop2) {
var newval = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
return newval;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.7/pixi.min.js"></script>
Try creating renderer using this code
const app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
resolution: 1,
});
But if you resize window after creating renderer, it won't be automatically resized to window size. To solve that you can listen to resize event
EDIT: Removing margins also might help.
body {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
display: block;
}
just found the answer and it was very silly thing actually.
the issue was with the width and the height they were set to window.innerWidth and window.innerHeight. my image quality was quite high so basically multiplying the width and the height inside PIXI.Application by 3 or 4 increases the qualit.
const app = new PIXI.Application({
width: window.innerWidth*3,
height: window.innerHeight*3,
resolution: 1,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.7/pixi.min.js"></script>

Rotating the whole canvas in fabricjs

Is there a way to rotate the canvas in fabric.js?
I am not looking to rotate each element, that can be achieved easily, but rather a way to rotate the whole canvas similar to what is achieved with canvas.rotate() on a native canvas element:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rotate(20*Math.PI/180);
Accessing the canvas element from fabric.js with getContext() is possible, but if I do that and then rotate it, only one of the two canvases is being rotate and the selection/drawing is severely off and drawing/selecting/etc is not working anymore either.
I am somewhat at a loss here. If this is something that's currently not possible with fabric.js I will create a ticket on github, but somehow it feels like it should be possible ...
[edit]
After the input from Ian I've figured a few things out and am at a point where I can rotate the canvas and get some results. However, objects are very far off from the correct position. However, this might be because, while rotating, I am also zooming and absolute paning the canvas (with canvas.setZoom() and canvas.absolutePan()). I think I'll create a ticket on GitHub and see what the devs think. Somewhat stuck here ... Just for reference here's the code snippet:
setAngle: function(angle) {
var self = this;
var canvas = self.getFabricCanvas();
var group = new fabric.Group();
var origItems = canvas._objects;
var size = self.getSize();
group.set({width: size.width, height: size.height, left: size.width / 2, top: size.height / 2, originX: 'center', originY: 'center', centeredRotation: true})
for (var i = 0; i < origItems.length; i++) {
group.add(origItems[i]);
}
canvas.add(group);
group.set({angle: (-1 * self.getOldAngle())});
canvas.renderAll();
group.set({angle: angle});
canvas.renderAll();
items = group._objects;
group._restoreObjectsState();
canvas.remove(group);
for (var i = 0; i < items.length; i++) {
canvas.add(items[i]);
canvas.remove(origItems[i]);
}
canvas.renderAll();
self.setOldAngle(angle);
},
As stated above, this function is called with two other functions:
setPosition: function(left, top) {
var self = this;
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
var point = new fabric.Point(left, top);
self.getFabricCanvas().absolutePan(point);
},
setZoom: function(zoom) {
var self = this;
self.getFabricCanvas().setZoom(zoom);
},
The functions are called through the following code:
MyClass.setZoom(1);
MyClass.setPosition(left, top);
MyClass.setZoom(zoom);
MyClass.setAngle(angle);
As you can see, I try to set the angle last, but it doesn't make a difference (at least not visually) when I do that. The zoom set to 1 at the beginning is important as otherwise the panning won't work properly.
Maybe someone has an idea ...
Here is how I did this (code based on this js fiddle).
rotate (degrees) {
let canvasCenter = new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2) // center of canvas
let radians = fabric.util.degreesToRadians(degrees)
canvas.getObjects().forEach((obj) => {
let objectOrigin = new fabric.Point(obj.left, obj.top)
let new_loc = fabric.util.rotatePoint(objectOrigin, canvasCenter, radians)
obj.top = new_loc.y
obj.left = new_loc.x
obj.angle += degrees //rotate each object by the same angle
obj.setCoords()
});
canvas.renderAll()
},
After doing this I also had to adjust the canvas background and size so objects wouldn't go off the canvas.

Creating an effect wherever mouse goes

So I have been trying endlessly to try and do something similar too what this site is doing (http://whois.domaintools.com/). I'm trying to get a webpage, so wherever the mouse moves over the webpage, that kind of effect follows it (I'm sorry I don't know what I would call the effect).
I've read how to ask questions on here, but I don't know what too look for so it's difficult for me to attempt this. So far this link (http://p5js.org/learn/demos/Hello_P5_Drawing.php) I've used the code from this and played around with it but i'm just puzzled as too how I would go about doing this.
Thanks for any help, I've been banging my head against a brick wall for a good couple of days now.
This seems to be some kind of particle system. I would start the following way: First create a class for a particle, it should have a random x and y coordinate, and it should change it's postion periodically to a random new postion. Then create a lot of instances of the particle and distribute them over the page.
http://jsfiddle.net/aggoh0s1/3/
/* each particle will move in a 100px100px square */
var gutterWidth = 100;
/* class definition */
var Particle = function(x, y) {
var t = this;
t.x = x;
t.y = y;
t.elem = $('<div class="particle" />');
t.elem.css({ left: x+"px", top: y+"px"});
$('body').append(t.elem);
/* create a new position every 500-1000 milliseconds */
var milliSecs = 500 + Math.random() * 500;
t.ptinterval = setInterval(function() {
var dx = Math.round(Math.random() * gutterWidth);
var dy = Math.round(Math.random() * gutterWidth);
t.elem.animate({left: (t.x + dx)+"px", top: (t.y + dy) + "px"}, 600);
}, milliSecs);
};
/* create a 1000px1000px area where particles are placed each 100px */
var particles = [];
var newParticle;
for(var x = 0; x < 1000; x = x + gutterWidth) {
for(var y = 0; y < 1000; y = y + gutterWidth) {
newParticle = new Particle(x,y);
particles.push(newParticle);
}
}
CSS:
.particle {
width: 2px;
height: 2px;
background-color: black;
position: absolute;
}
Using this logic, you could also use a canvas to display the particles instead of a html div like it is done on whois.domaintools.com. The next step should be to connect the particles with lines to each other, and after that some code should hide all particles that are some distance away from the mouse position.
I've developed the following solution for the effect which you are referring. This is done using jQuery using the event mousemove(). Bind this event to your body where the content is.
Method :
Create an element with the following css on your body. You can create the element onthefly using jQuery as well.
<div class='hover'></div>
CSS
.hover{
position:absolute;
width:100px;
height:100px;
background-color:#fff;
}
The add the following code to your page.
$('body').mousemove(function(event){
$('.hover').css({
'top' : event.pageY,
'left': event.pageX
})
});
The above code will bind an event to your mouse move and I change the element position according to the mouse coordinates.
This fiddle shows a running example
I've given you the basic idea of the solution! You will have to medle with the css and jquery to add the looks and feels of the effect which you refer to.
See the simple example
<img id="imgMove" src="Images/img1.jpg" height="100" width="100" style="position: absolute;" />
JQuery
$(document).ready(function () {
$(document).mousemove(function (e) {
$("#imgMove").css({ "top": e.pageY - 50, "left": e.pageX - 50 }); // e.pageX - Half of Image height, width
})
})

Javascript (jQuery) Scale an Image to Its Container's Center Point

This seems like it should be quite simple, but for some reason I can't quite wrap my brain around it. I have an image inside a "viewport" div, of which the overflow property is set to hidden.
I've implemented a simple zooming and panning with jQuery UI, however I am having trouble getting the zoom to appear to originate from the center of the viewport. I did a little screencast from Photoshop the effect I'm trying to reproduce: http://dl.dropbox.com/u/107346/share/reference-point-zoom.mov
In PS you can adjust the scaling reference point an the object will scale from that point. Obviously this is not possible with HTML/CSS/JS, so I'm trying to find the appropriate left and top CSS values to mimic the effect.
Here is the code in question, with a few unnecessary bits removed:
html
<div id="viewport">
<img id="map" src="http://dl.dropbox.com/u/107346/share/fake-map.png" alt="" />
</div>
<div id="zoom-control"></div>
javascript
$('#zoom-control').slider({
min: 300,
max: 1020,
value: 300,
step: 24,
slide: function(event, ui) {
var old_width = $('#map').width();
var new_width = ui.value;
var width_change = new_width - old_width;
$('#map').css({
width: new_width,
// this is where I'm stuck...
// dividing by 2 makes the map zoom
// from the center, but if I've panned
// the map to a different location I'd
// like that reference point to change.
// So instead of zooming relative to
// the map image center point, it would
// appear to zoom relative to the center
// of the viewport.
left: "-=" + (width_change / 2),
top: "-=" + (width_change / 2)
});
}
});
Here is the project on JSFiddle: http://jsfiddle.net/christiannaths/W4seR/
Here's the working solution. I will explain the logic at the next edit.
Function Logic:
Summary: Remember the center position of the image, relatively.
The calculations for width and height are similar, I will only explain the height calculationThe detailled explanation is just an example of function logic. The real code, with different variable names can be found at the bottom of the answer.
Calculate the center (x,y) of the #map, relative to #viewport. This can be done by using the offset(), height() and width() methods.
// Absolute difference between the top border of #map and #viewport
var differenceY = viewport.offset().top - map.offset().top;
// We want to get the center position, so add it.
var centerPosition = differenceY + viewport.height() * 0.5;
// Don't forget about the border (3px per CSS)
centerPosition += 3;
// Calculate the relative center position of #map
var relativeCenterY = centerPosition / map.height();
// RESULT: A relative offset. When initialized, the center of #map is at
// the center of #viewport, so 50% (= 0.5)
// Same method for relativeCenterX
Calculate the new top and left offsets:
// Calculate the effect of zooming (example: zoom 1->2 = 2)
var relativeChange = new_width / old_width;
// Calculate the new height
var new_height = relativeChange * old_height;
// Calculate the `top` and `left` CSS properties.
// These must be negative if the upperleft corner is outside he viewport
// Add 50% of the #viewport's height to correctly position #map
// (otherwise, the center will be at the upperleft corner)
var newTopCss = -relativeCenterY * new_height + 0.5 * viewport.height();
Change the CSS property
map.css("top", newTopCss);
Demo: http://jsfiddle.net/W4seR/12/
var map = $('#map');
var viewport = $('#viewport');
// Cache the size of the viewport (300x300)
var viewport_size = {
x: viewport.width(),
y: viewport.height()
};
map.draggable();
$('#zoom-control').slider({
min: 300,
max: 1020,
value: 300,
step: 24,
create: function() {
map.css({
'width': 300,
'left': 0,
'top': 0
});
},
slide: function(event, ui) {
var old_width = map.width();
var old_height = map.height();
var viewport_offset = viewport.offset();
var offset = map.offset();
offset = {
top: viewport_offset.top - offset.top + .5*viewport_size.y +3,
left: viewport_offset.left - offset.left + .5*viewport_size.x +3
};
// Relative offsets, relative to the center!
offset.top = offset.top / old_height;
offset.left = offset.left / old_width;
var new_width = ui.value;
var relative = new_width / old_width;
var new_height = relative * old_height;
offset = {
top: -offset.top * new_height + .5*viewport_size.y,
left: -offset.left * new_width + .5*viewport_size.x
};
var css_properties = {
width: new_width,
left: offset.left,
top: offset.top
};
map.css(css_properties);
trace((map.position().left));
}
});
I have always relied on the kindness of strangers. Pertinent changes:
// Calculate the offset as a percentage, accounting for the height of the window
var x_offset = ((map.position().left-150))/(old_width/2);
var y_offset = ((map.position().top-150))/(old_width/2);
var css_properties = {
width: new_width,
// Set the offset based on the existing percentage rather than 1/2
// then readjust for the height of the window
left: (new_width * x_offset /2 ) + 150 + "px",
top: (new_width * y_offset /2 ) + 150 + "px"
};
Replace the hardcoded 150 with a variable set on viewport instantiation if necessary.
Here is a quick working version:
http://jsfiddle.net/flabbyrabbit/chLkZ/
Probably not the neatest solution but seems to work nicely, hope it helps.
Update: sorry this only works if zoom is 0 when the map is moved.

Categories

Resources