how to animate a div within a boundry - javascript

To preface, this is my first time using JQuery so i dont really know the syntax and i'm a beginning programmer in javascript and I'm new to stackoverflow too. So apologies in advance.
Also apologies in advance if this has been asked before. This should be simple, yet somehow i can't figure it out and it's a little hard to search for.
Problem: I need my animation to be moving within a div boundry that it's in.
i tried changing the window in var h and var w to the id of my container, it doesn't work:
var h = $(#ghosts).height() - 75;
var w = $(#ghosts).height() - 75;
// html
<div id="playBoxProperties">
<div id="playBox"> // play area
<div id="ghosts"> // container for all 8 ghosts
<div id='g1' class="boo"> // one of the moving ghosts
</div>
</div>
</div>
</div>
// to randomize the movement
function randomPos() {
var h = $(window).height() - 75; // i need to change the window, to something else.
var w = $(window).width() - 75; // But i dont know what.
var newH = Math.floor(Math.random() * h);
var newW = Math.floor(Math.random() * w);
return [newH, newW];
}
// animation to move around
function animateDiv(divID) {
var newPos = randomPos();
$(divID).animate({ top: newPos[0], left: newPos[1] }, 4000, function () {
animateDiv(divID);
});
I expect it to be inside the black box

Subtract the currently iterating ghost element size from the random coordinate relative to the parent wrapper:
pass the parent and the animating child like randomPos($chi, $par)
Use Strings as your selectors. $(#ghosts); should be $('#ghosts');
Create a small jQuery plugin if you want: $.fn.animateGhosts. Use it like $ghosts.animateGhosts();
const rand = (min, max) => Math.random() * (max - min) + min;
const $ghostsWrapper = $('#ghosts');
const randomPos = ($chi, $par) => ({ // Randomize position
x: ~~(Math.random() * ($par.width() - $chi.width())),
y: ~~(Math.random() * ($par.height() - $chi.height()))
});
$.fn.animateGhosts = function() {
function anim() {
const pos = randomPos($(this), $ghostsWrapper);
$(this).stop().delay(rand(100, 500)).animate({
left: pos.x,
top: pos.y,
}, rand(1000, 4000), anim.bind(this));
}
return this.each(anim);
};
$('.boo').animateGhosts();
#ghosts {
position: relative;
height: 180px;
outline: 2px solid #000;
}
.boo {
position: absolute;
background: fuchsia;
width: 50px;
height: 50px;
}
<div id="ghosts">
<div class="boo">1</div>
<div class="boo">2</div>
<div class="boo">3</div>
<div class="boo">4</div>
<div class="boo">5</div>
<div class="boo">6</div>
</div>
<script src="//code.jquery.com/jquery-3.4.1.js"></script>
Better performance using CSS transition and translate
Here's an example that will use the power of CSS3 to move our elements in a hardware (GPU) accelerated fashion. Notice how, when slowing down, the elements are not zigzagging to the round pixel value (since jQuery animates top and left).
Instead we'll use transition for the CSS3 animation timing and translate(x, y) for the positions:
const rand = (min, max) => Math.random() * (max - min) + min;
const $ghostsWrapper = $('#ghosts');
const randomPos = ($chi, $par) => ({ // Randomize position
x: ~~(Math.random() * ($par.width() - $chi.width())),
y: ~~(Math.random() * ($par.height() - $chi.height()))
});
$.fn.animateGhosts = function() {
function anim() {
const pos = randomPos($(this), $ghostsWrapper);
$(this).css({
transition: `${rand(1, 4)}s ${rand(0.1, 0.4)}s ease`, // Speed(s) Pause(s)
transform: `translate(${pos.x}px, ${pos.y}px)`
}).one('transitionend', anim.bind(this));
}
return this.each(anim);
};
$('.boo').animateGhosts();
#ghosts {
position: relative;
height: 180px;
outline: 2px solid #000;
}
.boo {
position: absolute;
background: fuchsia;
width: 50px;
height: 50px;
}
<div id="ghosts">
<div class="boo">1</div>
<div class="boo">2</div>
<div class="boo">3</div>
<div class="boo">4</div>
<div class="boo">5</div>
<div class="boo">6</div>
</div>
<script src="//code.jquery.com/jquery-3.4.1.js"></script>

Related

Tilt element pane on mousemove

I'm trying to redo the animation that I saw on a site where an image changes it's x and y values with the movement of the mouse. The problem is that the origin of the mouse is in the top left corner and I'd want it to be in the middle.
To understand better, here's how the mouse axis values work :
Now here's how I'd want it to be:
sorry for the bad quality of my drawings, hope you understand my point from those ^^
PS: I'm having a problem while trying to transform the x y values at the same time and I don't know why.
Here's what I wrote in JavaScript :
document.onmousemove = function(e){
var x = e.clientX;
var y = e.clientY;
document.getElementById("img").style.transform = "rotateX("+x*0.005+"deg)";
document.getElementById("img").style.transform = "rotateY("+y*0.005+"deg)";
}
The exact 3D effect you're up to is called "tilting".
Long story short, it uses CSS transform's rotateX() and rotateY() on a child element inside a perspective: 1000px parent. The values passed for the rotation are calculated from the mouse/pointer coordinates inside the parent Element and transformed to a respective degree value.
Here's a quick simplified remake example of the original script:
const el = (sel, par) => (par || document).querySelector(sel);
const elWrap = el("#wrap");
const elTilt = el("#tilt");
const settings = {
reverse: 0, // Reverse tilt: 1, 0
max: 35, // Max tilt: 35
perspective: 1000, // Parent perspective px: 1000
scale: 1, // Tilt element scale factor: 1.0
axis: "", // Limit axis. "y", "x"
};
elWrap.style.perspective = `${settings.perspective}px`;
const tilt = (evt) => {
const bcr = elWrap.getBoundingClientRect();
const x = Math.min(1, Math.max(0, (evt.clientX - bcr.left) / bcr.width));
const y = Math.min(1, Math.max(0, (evt.clientY - bcr.top) / bcr.height));
const reverse = settings.reverse ? -1 : 1;
const tiltX = reverse * (settings.max / 2 - x * settings.max);
const tiltY = reverse * (y * settings.max - settings.max / 2);
elTilt.style.transform = `
rotateX(${settings.axis === "x" ? 0 : tiltY}deg)
rotateY(${settings.axis === "y" ? 0 : tiltX}deg)
scale(${settings.scale})
`;
}
elWrap.addEventListener("pointermove", tilt);
/*QuickReset*/ * {margin:0; box-sizing: border-box;}
html, body { min-height: 100vh; }
#wrap {
height: 100vh;
display: flex;
background: no-repeat url("https://i.stack.imgur.com/AuRxH.jpg") 50% 50% / cover;
}
#tilt {
outline: 1px solid red;
height: 80vh;
width: 80vw;
margin: auto;
background: no-repeat url("https://i.stack.imgur.com/wda9r.png") 50% 50% / contain;
}
<div id="wrap"><div id="tilt"></div></div>
Regarding your code:
Avoid using on* event handlers (like onmousemove). Use EventTarget.addEventListener() instead — unless you're creating brand new Elements from in-memory. Any additionally added on* listener will override the previous one. Bad programming habit and error prone.
You cannot use style.transform twice (or more) on an element, since the latter one will override any previous - and the transforms will not interpolate. Instead, use all the desired transforms in one go, using Transform Matrix or by concatenating the desired transform property functions like : .style.transform = "rotateX() rotateY() scale()" etc.
Disclaimer: The images used in the above example from the original problem's reference website https://cosmicpvp.com might be subject to copyright. Here are used for illustrative and educative purpose only.
You can find out how wide / tall the screen is:
const width = window.innerWidth;
const height = window.innerHeight;
So you can find the centre of the screen:
const windowCenterX = width / 2;
const windowCenterY = height / 2;
And transform your mouse coordinates appropriately:
const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;
Small demo:
const coords = document.querySelector("#coords");
document.querySelector("#area").addEventListener("mousemove", (event)=>{
const x = event.clientX;
const y = event.clientY;
const width = window.innerWidth;
const height = window.innerHeight;
const windowCenterX = width / 2;
const windowCenterY = height / 2;
const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;
coords.textContent = `x: ${transformedX}, y: ${transformedY}`;
});
body, html, #area {
margin: 0;
width: 100%;
height: 100%;
}
#area {
background-color: #eee;
}
#coords {
position: absolute;
left: 10px;
top: 10px;
}
<div id="area"></div>
<div id="coords"></div>
I think I would use the bounding rect of the image to determine the center based on the image itself rather than the screen... something like this (using CSSVars to handle the transform)
const img = document.getElementById('fakeimg')
addEventListener('pointermove', handler)
function handler(e) {
const rect = img.getBoundingClientRect()
const x1 = (rect.x + rect.width / 2)
const y1 = (rect.y + rect.height / 2)
const x2 = e.clientX
const y2 = e.clientY
let angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI) + 90
angle = angle < 0 ?
360 + angle :
angle
img.style.setProperty('--rotate', angle);
}
*,
*::before,
*::after {
box-sizeing: border-box;
}
html,
body {
height: 100%;
margin: 0
}
body {
display: grid;
place-items: center;
}
[id=fakeimg] {
width: 80vmin;
background: red;
aspect-ratio: 16 / 9;
--rotation: calc(var(--rotate) * 1deg);
transform: rotate(var(--rotation));
}
<div id="fakeimg"></div>

Calculate transform y-scale property as the function of elapsed time

I have a container that is expanded and collapsed on click of chevron icon. The code to collapse/expand the container is in the function transformAnimation. The code of transformAnimation is similar to the code on MDN web docs for requestAnimationFrame. The code to animate (scale) the container has been developed on the guidelines of this article on Building performant expand & collapse animations on Chrome Developers website.
I am not able to figure out how to calculate yScale value (which is nothing but y scale values for collapse/expand animation) as a function of the time elapsed since the start of the animation.
To elaborate what I mean, let's assume that the container is in expanded state. In this state the scaleY value of the container is 6. Now when user clicks on the toggle button, in the transformAnimation function for each animation frame, i.e, execution of the requestAnimationFrame callback step function, the value of scaleY should decrease from 6 (the expanded state) to 1 (the collapsed state) in the exact duration that I want the animation to run for.
In the present state, the code to calculate yScale is not working as expected.
const dragExpandableContainer = document.querySelector('.drag-expandable-container');
const dragExpandableContents = document.querySelector('.drag-expandable__contents');
const resizeableControlEl = document.querySelector('.drag-expandable__resize-control');
const content = document.querySelector(`.content`);
const toggleEl = document.querySelector(`.toggle`);
const collapsedHeight = calculateCollapsedHeight();
/* This height is used as the basis for calculating all the scales for the component.
* It acts as proxy for collapsed state.
*/
dragExpandableContainer.style.height = `${collapsedHeight}px`;
// Apply iniial transform to expand
dragExpandableContainer.style.transformOrigin = 'bottom left';
dragExpandableContainer.style.transform = `scale(1, 10)`;
// Apply iniial reverse transform on the contents
dragExpandableContents.style.transformOrigin = 'bottom left';
dragExpandableContents.style.transform = `scale(1, calc(1/10))`;
let isOpen = true;
const togglePopup = () => {
if (isOpen) {
collapsedAnimation();
toggleEl.classList.remove('toggle-open');
isOpen = false;
} else {
expandAnimation();
toggleEl.classList.add('toggle-open');
isOpen = true
};
};
function calculateCollapsedHeight() {
const collapsedHeight = content.offsetHeight + resizeableControlEl.offsetHeight;
return collapsedHeight;
}
const calculateCollapsedScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = dragExpandableContainer.getBoundingClientRect().height;
return {
/* Since we are not dealing with scaling on X axis, we keep it 1.
* It can be inverse to if required */
x: 1,
y: expandedHeight / collapsedHeight,
};
};
const calculateExpandScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = 100;
return {
x: 1,
y: expandedHeight / collapsedHeight,
};
};
function expandAnimation() {
const {
x,
y
} = calculateExpandScale();
transformAnimation('expand', {
x,
y
});
}
function collapsedAnimation() {
const {
x,
y
} = calculateCollapsedScale();
transformAnimation('collapse', {
x,
y
});
}
function transformAnimation(animationType, scale) {
let start, previousTimeStamp;
let done = false;
function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
const elapsed = timestamp - start;
if (previousTimeStamp !== timestamp) {
const count = Math.min(0.1 * elapsed, 200);
//console.log('count', count);
let yScale;
if (animationType === 'expand') {
yScale = (scale.y / 100) * count;
} else yScale = scale.y - (scale.y / 100) * count;
//console.log('yScale', yScale);
if (yScale < 1) yScale = 1;
dragExpandableContainer.style.transformOrigin = 'bottom left';
dragExpandableContainer.style.transform = `scale(${scale.x}, ${yScale})`;
const inverseXScale = 1;
const inverseYScale = 1 / yScale;
dragExpandableContents.style.transformOrigin = 'bottom left';
dragExpandableContents.style.transform = `scale(${inverseXScale}, ${inverseYScale})`;
if (count === 200) done = true;
//console.log('elapsed', elapsed);
if (elapsed < 1000) {
// Stop the animation after 2 seconds
previousTimeStamp = timestamp;
if (!done) requestAnimationFrame(step);
}
}
}
requestAnimationFrame(step);
}
.drag-expandable-container {
position: absolute;
bottom: 0px;
display: block;
overflow: hidden;
width: 100%;
background-color: #f3f7f7;
}
.drag-expandable__contents {
height: 0;
}
.toggle {
position: absolute;
top: 2px;
right: 15px;
height: 10px;
width: 10px;
transition: transform 0.2s linear;
}
.toggle-open {
transform: rotate(180deg);
}
.drag-expandable__resize-control {
background-color: #e7eeef;
}
.burger-icon {
width: 12px;
margin: 0 auto;
padding: 2px 0;
}
.burger-icon__line {
height: 1px;
background-color: #738F93;
margin: 2px 0;
}
.drag-expandable__resize-control:hover {
border-top: 1px solid #4caf50;
cursor: ns-resize;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css.css">
</head>
<body>
<div class="drag-expandable-container">
<div class="drag-expandable__contents">
<div class="drag-expandable__resize-control">
<div class="burger-icon">
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
</div>
</div>
<div class="content" />
<div>
<div class="toggle toggle-open" onclick="togglePopup()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M416 352c-8.188 0-16.38-3.125-22.62-9.375L224 173.3l-169.4 169.4c-12.5 12.5-32.75 12.5-45.25 0s-12.5-32.75 0-45.25l192-192c12.5-12.5 32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25C432.4 348.9 424.2 352 416 352z"/></svg>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="js.js"></script>
</html>

Zoom towards are mouse is over

I have found this script online which i though would help me to zoom in on an image, but zoom towards the mouse position. However when the mouse is positioned to the left this isn't the case. I feel like there is a simple change I need to make however I can't find it!
window.addEventListener('load', () => {
new Vue({}).$mount('#app');
});
Vue.component('test', {
template: '#template',
data() {
return {
zoomMin: 1,
zoomMax: 7,
dragEventX: null,
dragEventY: null,
touchEvent: null,
zoomPointX: 0,
zoomPointY: 0,
zoomScale: 1,
zoomStyle: null,
frame: 1,
speed: 1,
zoom: 1,
}
},
mounted() {
this.$refs.image.addEventListener('wheel', this.onWheel);
},
methods: {
onWheel($event) {
$event.preventDefault();
let direction = Math.sign($event.deltaY);
let n = this.zoomScale - direction / (6 / this.speed);
this.setZoomScale($event.clientX, $event.clientY, n)
},
setZoomScale(clientX, clientY, n) {
const bounding = this.$refs.image.getBoundingClientRect();
let mouseLeft = clientX - bounding.left,
mouseTop = clientY - bounding.top,
zoomPointX = this.zoomPointX || 0,
zoomPointY = this.zoomPointY || 0;
let leftPoint = (mouseLeft - zoomPointX) / this.zoomScale,
topPoint = (mouseTop - zoomPointY) / this.zoomScale;
this.zoomScale = Math.min(Math.max(n, this.zoomMin), this.zoomMax);
let leftZoom = -leftPoint * this.zoomScale + mouseLeft,
topZoom = -topPoint * this.zoomScale + mouseTop;
this.setZoomPoint(leftZoom, topZoom);
},
setZoomPoint(leftZoom, topZoom) {
let left = leftZoom || this.zoomPointX || 0,
top = topZoom || this.zoomPointY || 0,
leftOffset = this.$el.clientWidth * (this.zoomScale - 1),
topOffset = this.$el.clientHeight * (this.zoomScale - 1);
this.zoomPointX = Math.min(Math.max(left, -leftOffset), 0);
this.zoomPointY = Math.min(Math.max(top, -topOffset), 0);
this.setZoomStyle();
},
setZoomStyle() {
this.zoomStyle = {
transform: `translate(${this.zoomPointX}px, ${this.zoomPointY}px) scale(${this.zoomScale})`
};
},
}
});
#app {
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 80%;
overflow: hidden;
border: 1px solid #000;
}
.container img {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<test></test>
</div>
<script type="text/x-template" id="template">
<div class="container">
<img ref="image" :style="zoomStyle" src="https://s3-eu-west-1.amazonaws.com/crash.net/visordown.com/field/image/2020_YAM_YZF1000R1_EU_DPBMC_STA_001-70516%20copy.jpg"></img>
</div>
</script>
You do not zoom towards the mouse on the right either; in fact, if you put your mouse in the top-left corner you zoom in to the center of the image. Besides that I can at least identify that this.zoomPointX = Math.min(Math.max(left, -leftOffset), 0); only allows your translate to go left, and thus only allow zooming towards the right of the center line.
Some debugging
Let's start with taking a step back and figuring out what we are doing here. The final styling is a translate, followed by a scale. The scale happens around the center of what you are seeing on screen right then, and the translate that happens before that is meant to move the image so the point around which you want to zoom is in the middle.
To properly debug this we need a better understanding of what the code is doing, so I first added some debug markers and disabled the zoom styling, so we can visualise what is happening without it zooming all over the place.
window.addEventListener('load', () => {
new Vue({}).$mount('#app');
});
Vue.component('test', {
template: '#template',
data() {
return {
zoomMin: 1,
zoomMax: 7,
dragEventX: null,
dragEventY: null,
touchEvent: null,
zoomPointX: 0,
zoomPointY: 0,
zoomScale: 1,
zoomStyle: null,
frame: 1,
speed: 1,
zoom: 1,
// Debugging
imageHeight: 0,
imageWidth: 0,
}
},
computed: {
markerStyle() {
return {
top: `${this.imageHeight / 2 - this.zoomPointY}px`,
left: `${this.imageWidth / 2 - this.zoomPointX}px`,
};
},
boundaryMarkerStyle() {
const middleY = this.imageHeight / 2 - this.zoomPointY;
const middleX = this.imageWidth / 2 - this.zoomPointX;
const height = this.imageHeight / this.zoomScale;
const width = this.imageWidth / this.zoomScale;
return {
top: `${middleY - height / 2}px`,
left: `${middleX - width / 2}px`,
width: `${width}px`,
height: `${height}px`,
};
},
},
mounted() {
// Moved the listener to the container so we can overlay something over the image
this.$refs.container.addEventListener("wheel", this.onWheel);
// Temporary for debugging; I could also determine it dynamically and should if we
// we want to be able to resize
this.$refs.image.addEventListener("load", () => {
const bounding = this.$refs.image.getBoundingClientRect();
this.imageHeight = bounding.height;
this.imageWidth = bounding.width;
});
},
methods: {
onWheel($event) {
$event.preventDefault();
let direction = Math.sign($event.deltaY);
let n = this.zoomScale - direction / (6 / this.speed);
this.setZoomScale($event.clientX, $event.clientY, n)
},
setZoomScale(clientX, clientY, n) {
const bounding = this.$refs.image.getBoundingClientRect();
// mouseLeft and mouseTop represent the pixel within the container we are targeting
const mouseLeft = clientX - bounding.left;
const mouseTop = clientY - bounding.top;
// zoomPointX and Y represent what point we were zooming towards
const zoomPointX = this.zoomPointX || 0;
const zoomPointY = this.zoomPointY || 0;
// This attempts to modify the point we are targeting based on
// what we are zooming towards before and what we are zooming towards now
// zoomPointX represents something that is calculated with a different zoomScale, so this
// presumably calculates bogus
const leftPoint = (mouseLeft - zoomPointX) / this.zoomScale;
const topPoint = (mouseTop - zoomPointY) / this.zoomScale;
// This normalizes the zoom so we can't zoom out past the full image and not past 7 times the current image
this.zoomScale = Math.min(Math.max(n, this.zoomMin), this.zoomMax);
// This should represent the point we are zooming towards (I think?)
const leftZoom = -leftPoint * this.zoomScale + mouseLeft;
const topZoom = -topPoint * this.zoomScale + mouseTop;
// This function breaks its promise to set only the zoom scale and also sets the zoom point. :(
this.setZoomPoint(leftZoom, topZoom);
},
setZoomPoint(leftZoom, topZoom) {
const left = leftZoom || this.zoomPointX || 0;
const top = topZoom || this.zoomPointY || 0;
const leftOffset = this.$el.clientWidth * (this.zoomScale - 1);
const topOffset = this.$el.clientHeight * (this.zoomScale - 1);
this.zoomPointX = Math.min(Math.max(left, -leftOffset), 0);
this.zoomPointY = Math.min(Math.max(top, -topOffset), 0);
this.setZoomStyle();
},
setZoomStyle() {
// this.zoomStyle = {
// transform: `translate(${this.zoomPointX}px, ${this.zoomPointY}px) scale(${this.zoomScale})`
// };
},
}
});
#app {
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 80%;
overflow: hidden;
border: 1px solid #000;
position: relative;
}
.container img {
width: 100%;
}
.marker {
position: absolute;
border: 8px solid red;
z-index: 9999;
}
.boundary-marker {
position: absolute;
border: 2px dashed red;
z-index: 9999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<test></test>
</div>
<script type="text/x-template" id="template">
<div class="container" ref="container">
<img ref="image" :style="zoomStyle" src="https://s3-eu-west-1.amazonaws.com/crash.net/visordown.com/field/image/2020_YAM_YZF1000R1_EU_DPBMC_STA_001-70516%20copy.jpg"></img>
<div class="marker" :style="markerStyle"></div>
<div class="boundary-marker" :style="boundaryMarkerStyle"></div>
</div>
</script>
Fixing the center point
First of all, lets disable the functionality to clamp the zoom point, as we know it to be broken.
this.zoomPointX = left;
this.zoomPointY = top;
Then lets focus on getting the center point right. To get this right, we need to actually determine which pixel of the original image we are targeting, taking into account we might have zoomed in already! We need to keep in mind that the translate function and the scale function always are on the original image.
We can determine the part of the image we are currently viewing with zoomPointX, zoomPointY and zoomScale. (hint: we did that for the debug marker already) zoomPointX and zoomPointY do not really represent a point we zoom towards, but more the translation we made, so I have renamed them to translateX and translateY for convenience.
The x coordinate of the pixel in the original picture we can currently see on the left side of the screen is calculated by finding the x coordinate of the middle point on the original image, then subtracting half of our viewport from it:
const leftSideX = (this.imageWidth / 2 - this.translateX) - (this.imageWidth / this.zoomScale / 2);
The number of pixels from the left side of our viewport to the point, as it would be on the original picture can be calculated by multiplying the amount of pixels this viewport represents with the percentage of pixels from the left border we are
const offsetX = (this.imageWidth / this.zoomScale) * (mouseLeft / this.imageWidth);
And then we get our translateX by calculating from the middle of the image again.
this.translateX = -(leftSideX + offsetX - (this.imageWidth / 2));
window.addEventListener('load', () => {
new Vue({}).$mount('#app');
});
Vue.component('test', {
template: '#template',
data() {
return {
zoomMin: 1,
zoomMax: 7,
dragEventX: null,
dragEventY: null,
touchEvent: null,
translateX: 0,
translateY: 0,
zoomScale: 1,
zoomStyle: null,
frame: 1,
speed: 1,
zoom: 1,
// Debugging
imageHeight: 0,
imageWidth: 0,
}
},
computed: {
markerStyle() {
return {
top: `${this.imageHeight / 2 - this.translateY}px`,
left: `${this.imageWidth / 2 - this.translateX}px`,
};
},
boundaryMarkerStyle() {
const middleY = this.imageHeight / 2 - this.translateY;
const middleX = this.imageWidth / 2 - this.translateX;
const height = this.imageHeight / this.zoomScale;
const width = this.imageWidth / this.zoomScale;
return {
top: `${middleY - height / 2}px`,
left: `${middleX - width / 2}px`,
width: `${width}px`,
height: `${height}px`,
};
},
},
mounted() {
// Moved the listener to the container so we can overlay something over the image
this.$refs.container.addEventListener("wheel", this.onWheel);
// Temporary for debugging; I could also determine it dynamically and should if we
// we want to be able to resize
this.$refs.image.addEventListener("load", () => {
const bounding = this.$refs.image.getBoundingClientRect();
this.imageHeight = bounding.height;
this.imageWidth = bounding.width;
});
},
methods: {
onWheel($event) {
$event.preventDefault();
const direction = Math.sign($event.deltaY);
const scale = this.zoomScale - direction / (6 / this.speed);
this.setZoomScale($event.clientX, $event.clientY, scale);
},
setZoomScale(clientX, clientY, scale) {
const bounding = this.$refs.image.getBoundingClientRect();
// mouseLeft and mouseTop represent the pixel within the container we are targeting
const mouseLeft = clientX - bounding.left;
const mouseTop = clientY - bounding.top;
// translateX and Y represent the translation towards the point we are zooming towards
const leftSideX =
this.imageWidth / 2 -
this.translateX -
this.imageWidth / this.zoomScale / 2;
const offsetX =
(this.imageWidth / this.zoomScale) * (mouseLeft / this.imageWidth);
this.translateX = -(leftSideX + offsetX - this.imageWidth / 2);
const leftSideY =
this.imageHeight / 2 -
this.translateY -
this.imageHeight / this.zoomScale / 2;
const offsetY =
(this.imageHeight / this.zoomScale) * (mouseTop / this.imageHeight);
this.translateY = -(leftSideY + offsetY - this.imageHeight / 2);
// This normalizes the zoom so we can't zoom out past the full image and not past 7 times the current image
this.zoomScale = Math.min(Math.max(scale, this.zoomMin), this.zoomMax);
},
}
});
#app {
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 80%;
overflow: hidden;
border: 1px solid #000;
position: relative;
}
.container img {
width: 100%;
}
.marker {
position: absolute;
border: 8px solid red;
z-index: 9999;
}
.boundary-marker {
position: absolute;
border: 2px dashed red;
z-index: 9999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<test></test>
</div>
<script type="text/x-template" id="template">
<div class="container" ref="container">
<img ref="image" :style="zoomStyle" src="https://s3-eu-west-1.amazonaws.com/crash.net/visordown.com/field/image/2020_YAM_YZF1000R1_EU_DPBMC_STA_001-70516%20copy.jpg"></img>
<div class="marker" :style="markerStyle"></div>
<div class="boundary-marker" :style="boundaryMarkerStyle"></div>
</div>
</script>
Fixing the bounds and doing cleanup
The original min-max function that we removed earlier was, I think meant to prevent you from zooming to a point where you see white on the outside of the image (ala what did happen in the original when you zoomed on the bottom right).
We can do this by clamping the value of translateX and translateY to an imaginary rectangle that is half the width/half the height from each of the relevant edges. We start by determining the height/width of our viewport (hint: we already calculated this for the marker). The center is (0, 0), while the edges are variations of (+/- imageWidth / 2, +/- imageHeight / 2).
Afterwards, we just need to clamp the value.
const viewportHeight = this.imageHeight / this.zoomScale;
const viewportWidth = this.imageWidth / this.zoomScale;
const exclusionViewportY = (this.imageHeight / 2) - (viewportHeight / 2);
const exclusionViewportX = (this.imageWidth / 2) - (viewportWidth / 2);
this.translateX = Math.min(Math.max(this.translateX, -exclusionViewportX), exclusionViewportX);
this.translateY = Math.min(Math.max(this.translateY, -exclusionViewportY), exclusionViewportY);
Finally, I took the liberty to move zoomStyle to a computed property. It saves on having to call functions in methods that have nothing to do with that method like you did. I renamed the main function to better represent what it does. I also added a beforeDestroy lifecycle hook, because your code currently leaks memory as the event handler is not removed.
window.addEventListener('load', () => {
new Vue({}).$mount('#app');
});
Vue.component('test', {
template: '#template',
data() {
return {
zoomMin: 1,
zoomMax: 7,
dragEventX: null,
dragEventY: null,
touchEvent: null,
translateX: 0,
translateY: 0,
zoomScale: 1,
frame: 1,
speed: 1,
zoom: 1,
};
},
computed: {
zoomStyle() {
return {
transform: `translate(${this.translateX}px, ${this.translateY}px) scale(${this.zoomScale})`,
};
},
},
mounted() {
// Moved the listener to the container so we can overlay something over the image
this.$refs.container.addEventListener("wheel", this.onWheel);
},
methods: {
onWheel($event) {
$event.preventDefault();
const direction = Math.sign($event.deltaY);
const scale = this.zoomScale - direction / (6 / this.speed);
this.calculateZoom($event.clientX, $event.clientY, scale);
},
calculateZoom(clientX, clientY, scale) {
const bounding = this.$refs.image.getBoundingClientRect();
// mouseLeft and mouseTop represent the pixel within the container we are targeting
const mouseLeft = clientX - bounding.left;
const mouseTop = clientY - bounding.top;
// translateX and Y represent the translation towards the point we are zooming towards
const leftSideX =
bounding.width / 2 -
this.translateX -
bounding.width / this.zoomScale / 2;
const offsetX =
(bounding.width / this.zoomScale) * (mouseLeft / bounding.width);
this.translateX = -(leftSideX + offsetX - bounding.width / 2);
const leftSideY =
bounding.height / 2 -
this.translateY -
bounding.height / this.zoomScale / 2;
const offsetY =
(bounding.height / this.zoomScale) * (mouseTop / bounding.height);
this.translateY = -(leftSideY + offsetY - bounding.height / 2);
// This normalizes the zoom so we can't zoom out past the full image and not past 7 times the current image
this.zoomScale = Math.min(Math.max(scale, this.zoomMin), this.zoomMax);
// Finally, we clamp the center point so we always stay within the image
const viewportHeight = bounding.height / this.zoomScale;
const viewportWidth = bounding.width / this.zoomScale;
const exclusionViewportY = bounding.height / 2 - viewportHeight / 2;
const exclusionViewportX = bounding.width / 2 - viewportWidth / 2;
this.translateX = Math.min(
Math.max(this.translateX, -exclusionViewportX),
exclusionViewportX
);
this.translateY = Math.min(
Math.max(this.translateY, -exclusionViewportY),
exclusionViewportY
);
},
}
});
#app {
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 80%;
overflow: hidden;
border: 1px solid #000;
position: relative;
padding: 0;
margin: 0;
line-height: 0;
}
.container img {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<test></test>
</div>
<script type="text/x-template" id="template">
<div class="container" ref="container">
<img ref="image" :style="zoomStyle" src="https://s3-eu-west-1.amazonaws.com/crash.net/visordown.com/field/image/2020_YAM_YZF1000R1_EU_DPBMC_STA_001-70516%20copy.jpg"></img>
</div>
</script>

2D Infinitely looping Array of elements

The Goal :
The idea is to create an element grid (image gallery for exemple) that would infinitely loop on itself scrolling on two axes.
There should be no holes nor too much randomness (avoid having the same element randomly falling aside from itself). And this no matter how many element there is in the first place (it seems easy to infinite loop through a grid of 16 (4*4) elements, not that much over 17 (17*1). (My guess is that any prime number of elements is by definition a pain to make a grid of).
So I actually found a wonderful working exemple :
http://www.benstockley.com/
It's actually really close (probably better) than what I was imagining. Now it's using canvas and i tried looking at the javascript and it's a 30000 minified lines long script so I really can't read any core logic behind it.
Math side / Problem solving :
This is the logic and theory behind the problem, the math involved and the mindset.
How the program should process the list of elements so we have no holes, infinite grid, best repartion of the elements over all the axes.
My guess is that it somehow has to be procedural. I'm not sure if we should create grids or loop through the list on every axes (kind of like sudoku ? i don't know);
Pratical side / UI / UX :
Any advice on the technologies involved, pieces of code. I'm guessing it classic DOM is out of the way and that somehow canvas or 2D webgl will be mandatory. But I would love to hear any advice on this side.
Besides all the elements grid processing. The UI and UX involved in exploring a 2D infinite or vast layout in DOM or renderer is somehow not classical. The best technologies or advice on doing this are welcome.
Exemples :
I would welcome any working exemple that somewhat share an aspect of this problem.
I've got a fiddle that's set up to arrange your 2d grid.
It functions by using horizontal and vertical "step sizes". So, moving one step right in the grid advances the horizontal step size in the list. Moving one step down advances the vertical step size in the list (and they accumulate).
We allow the advances in the list to loop back to zero when the end is reached.
It likely makes sense to use a horizontal step size of 1 (so a row of your grid will maintain your list order). For the vertical step size, you want an integer that shares no common divisors with the list length. Though it's no guarantee, I used the (rounded) square root of the list length as something that will work in lots of cases.
I'll reproduce the fiddle here:
var list = ['red','green','blue','cyan','orange','yellow','pink'];
var hstep = 1;
var vstep = Math.ceil(Math.sqrt(list.length));
function getListItem(x,y) {
var index = x * hstep + y * vstep;
return list[index % list.length];
}
var elementSize = 30;
var gutterSize = 10;
function getOffset(x,y) {
return [10 + (elementSize + gutterSize) * x, 10 + (elementSize + gutterSize) * y];
}
var frame = $('.frame');
function drawElement(x,y) {
var listItem = getListItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': listItem
});
frame.append(element);
}
function drawElements() {
var x = 0, y = 0;
while (10 + (elementSize + gutterSize) * x < frame.width()) {
while (10 + (elementSize + gutterSize) * y < frame.height()) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
drawElements();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 300px;
position: relative;
overflow: hidden;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
.buttons {
position: absolute;
top: 0px;
width: 100%;
}
.buttons button {
position: absolute;
width: 30px;
height: 30px;
padding: 5px;
}
button.up {top: 0px; left: 46%;}
button.down {top: 355px; left: 46%;}
button.left {top: 160px; left: 15px;}
button.right {top: 160px; right: 15px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
</div>
<div class="buttons">
<button class="up">↑</button>
<button class="down">↓</button>
<button class="left">←</button>
<button class="right">→</button>
</div>
You can see I've left some simple buttons to implement movement, but they are not functional yet. If you wanted to continue implementation along the lines of what I've done here, you could render your elements to a certain range beyond the visible frame, then implement some sort of animated repositioning. The renderElements function here only renders what is visible, so you can use something like that and not get stuck in rendering infinite elements, even though there's no theoretical limit to how far you can "scroll".
#arbuthnott I edited your code to implement the exploration via decrementing relativeX and relativeY variables. Also I inserted an "origin" div (1x1 px, overflow visible). This DOM element will represent the X and Y origin. I'm not sure it's essential but it's really convenient.
Now my function currently remove all elements and reinsert all elements on each update (every 500ms for now).
The idear would be to find a way to compare which elements I need versus which one already exists.
Maybe storing existing elements into an array, and compare the array with the "query" array. Than see just the elements that are missing.
This is the idear, not sure about the implementation (I suck at handling arrays).
https://jsfiddle.net/bnv6mumd/64/
var sources = ['red','green','blue','cyan','orange','yellow','pink','purple'];
var frame = $('.frame'),
origin = $('.origin');
var fWidth = 600,
fHeight = 300,
srcTotal = sources.length,
srcSquare = Math.ceil(Math.sqrt(srcTotal)),
rX = 0,
rY = 0;
var gridSize = 30,
gutterSize = 5,
elementSize = gridSize - gutterSize;
function getSourceItem(x,y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x,y) {
return [gridSize * x,gridSize * y];
}
function drawElement(x,y) {
var sourceItem = getSourceItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': sourceItem,
});
origin.append(element);
}
function init() {
var x = 0, y = 0;
while ( gridSize * x < fWidth) {
while ( gridSize * y < fHeight) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
function updateElements() {
origin.empty();
var x = -Math.trunc(rX / gridSize) -1, y = - Math.trunc(rY / gridSize) -1;
while ( gridSize * x + rX < fWidth) {
while ( gridSize * y + rY < fHeight) {
drawElement(x,y);
y++;
}
y = -Math.ceil(rY / gridSize);
x++;
}
}
function animate() {
rX -= 5;
rY -= 5;
origin.css({left: rX, top: rY})
updateElements();
console.log("relative X : " + rX + " | relative Y : " + rY);
}
setInterval(animate, 500)
init();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 600px;
position: relative;
overflow: hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 25px;
height: 25px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
<div class="origin" style="top:0;left:0;"></div>
</div>
This is my final snippet version (i will start to work on real implementation specific to my case now).
I think I optimized in a decent way DOM operations, code structure etc (I am very well open to suggestions though).
I now only update the elements that needs to be updated (click near the frame to show overflow)
https://jsfiddle.net/bnv6mumd/81/
var sources = ['red', 'green', 'blue', 'cyan', 'orange', 'yellow', 'pink', 'purple'];
var frame = $('.frame'),
origin = $('.origin');
var srcTotal = sources.length,
srcSquare = Math.round(Math.sqrt(srcTotal)),
fWidth = 200,
fHeight = 200,
cellSize = 50,
gutterSize = 20,
gridSize = [Math.floor(fWidth / cellSize) + 1, Math.floor(fHeight / cellSize) + 1],
aX = 0, // Absolute/Applied Coordinates
aY = 0,
rX = 0, // Relative/frame Coordinates
rY = 0;
function getSrcItem(x, y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x, y) {
return [cellSize * x, cellSize * y];
}
function getY() {
return Math.floor(-rY / cellSize);
}
function getX() {
return Math.floor(-rX / cellSize);
}
function drawElement(x, y) {
var srcItem = getSrcItem(x, y),
offsets = getOffset(x, y),
element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': srcItem,
}).attr({
"X": x,
"Y": y
});
origin.append(element);
}
function drawCol(x, y) {
var maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawElement(x - 1, y - 1);
y++;
}
}
function drawLign(x, y) {
var maxX = x + gridSize[0];
while (x <= maxX + 1) {
drawElement(x - 1, y - 1);
x++;
}
}
function drawGrid() {
origin.empty();
var x = getX(),
y = getY(),
maxX = x + gridSize[0],
maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawLign(x, y);
x = getX();
y++;
}
}
function updateX(x, y, diffX, diffY) {
if (Math.sign(diffX) == -1) {
drawCol(aX - 1, y);
$('[x=' + (aX + gridSize[0]) + ']').remove();
aX--;
} else if (Math.sign(diffY) == 1) {
drawCol(aX + gridSize[0] + 2, y);
$('[x=' + (aX - 1) + ']').remove();
aX++;
}
}
function updateY(x, y, diffX, diffY) {
if (Math.sign(diffY) == -1) {
drawLign(x, aY - 1);
$('[y=' + (aY + gridSize[0]) + ']').remove();
aY--;
} else if (Math.sign(diffY) == 1) {
drawLign(x, aY + gridSize[0] + 2);
$('[y=' + (aY - 1) + ']').remove();
aY++;
}
}
function animate() {
rX += 1;
rY += 1;
origin.css({
left: rX,
top: rY
});
var x = getX(),
y = getY(),
diffX = x - aX,
diffY = y - aY;
if (diffX) {
updateX(x, y, diffX, diffY)
};
if (diffY) {
updateY(x, y, diffX, diffY)
};
requestAnimationFrame(animate);
}
$('body').click(function() {
$(frame).toggleClass("overflow");
})
drawGrid();
animate();
.frame {
border: 2px solid black;
margin: 100px auto;
height: 200px;
width: 200px;
position: relative;
}
.overflow{
overflow:hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame overflow">
<div class="origin" style="top:0;left:0;"></div>
</div>

Make several objects rotate on same orbit but with different positions using jQuery

Hello I was trying to find a solution for a small script that can rotate objects around 1 center but it seems a bit too tricky for me.
I've found almost perfect solution and tried to modify it to suit my needs but there's a problem.
I'm trying to make 3 objects with text to rotate with same speed, same orbit but different start positions as if they were apexes(vertices) of equilateral triangle.
Here's my fiddle so far:
( function ( $ ) {
jQuery.fn.orbit = function(s, options){
var settings = {
orbits: 1 // Number of times to go round the orbit e.g. 0.5 = half an orbit
,period: 3000 // Number of milliseconds to complete one orbit.
,maxfps: 25 // Maximum number of frames per second. Too small gives "flicker", too large uses lots of CPU power
,clockwise: false // Direction of rotation.
};
$.extend(settings, options); // Merge the supplied options with the default settings.
return(this.each(function(){
var p = $(this);
/* First obtain the respective positions */
var p_top = p.css('top' ),
p_left = p.css('left'),
s_top = s.css('top' ),
s_left = s.css('left');
/* Then get the positions of the centres of the objects */
var p_x = parseInt(p_top ) + p.height()/2,
p_y = parseInt(p_left) + p.width ()/2,
s_x = parseInt(s_top ) + s.height()/2,
s_y = parseInt(s_left) + s.width ()/2;
/* Find the Adjacent and Opposite sides of the right-angled triangle */
var a = s_x - p_x,
o = s_y - p_y;
/* Calculate the hypotenuse (radius) and the angle separating the objects */
var r = Math.sqrt(a*a + o*o);
var theta = Math.acos(a / r);
/* Calculate the number of iterations to call setTimeout(), the delay and the "delta" angle to add/subtract */
var niters = Math.ceil(Math.min(4 * r, settings.period, 0.001 * settings.period * settings.maxfps));
var delta = 2*Math.PI / niters;
var delay = settings.period / niters;
if (! settings.clockwise) {delta = -delta;}
niters *= settings.orbits;
/* create the "timeout_loop function to do the work */
var timeout_loop = function(s, r, theta, delta, iter, niters, delay, settings){
setTimeout(function(){
/* Calculate the new position for the orbiting element */
var w = theta + iter * delta;
var a = r * Math.cos(w);
var o = r * Math.sin(w);
var x = parseInt(s.css('left')) + (s.height()/2) - a;
var y = parseInt(s.css('top' )) + (s.width ()/2) - o;
/* Set the CSS properties "top" and "left" to move the object to its new position */
p.css({top: (y - p.height()/2),
left: (x - p.width ()/2)});
/* Call the timeout_loop function if we have not yet done all the iterations */
if (iter < (niters - 1)) timeout_loop(s, r, theta, delta, iter+1, niters, delay, settings);
}, delay);
};
/* Call the timeout_loop function */
timeout_loop(s, r, theta, delta, 0, niters, delay, settings);
}));
}
}) (jQuery);
$('#object1' ).orbit($('#center' ), {orbits: 2, period: 8000});
$('#object2' ).orbit($('#center' ), {orbits: 4, period: 4000});
$('#object3' ).orbit($('#center' ), {orbits: 8, period: 2000});
HTML:
<h1> Example</h1>
<div id='rotation'>
<div id='center' >C</div>
<div id='object1' >Text1</div>
<div id='object2' >Text2</div>
<div id='object3' >Text3</div>
</div>
CSS:
#rotation {position: relative; width: 600px; height: 600px; background-color: #898989}
#center {position: absolute; width: 20px; height: 20px;
top: 300px; left: 300px; background-color: #ffffff;
-moz-border-radius: 40px; border-radius: 40px;
text-align: center; line-height: 15px;
}
#object1 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object2 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object3 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
I used different speed and revolutions for each object because I can't figure out how to set different start positions without messing up. If I touch x,y coordinates of any object in CSS then the orbiting messes up. It seems that object positions calculations are connected. And if I change coordinates then the calculation also changes. But I can't figure out how to fix this.
If you change the starting position of each element and call the .orbit function on each of them passing the same arguments it should work.
Check this out: fiddle
This is a slightly modified version of your fiddle. (Changed the starting positions and the arguments when calling the .orbit function.
Correct me if I'm wrong or if I didn't answer your question.
Got it working as intended
Fiddle
Jquery code:
var txt = $('#text .txt'), txtLen = txt.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 0, speed=0.1, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txtLen; i++) {
var a = angle - (i * 360 / txtLen);
$(txt[i]).css({top: r+(Math.sin(deg2rad(a))*r), left: r+(Math.cos(deg2rad(a))*r)});
}
angle = (angle - speed) % 360;
setTimeout(rotate, delay);
})();
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();
var txt2 = $('#text2 .txt2'), txt2Len = txt2.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 13, speed=0.2, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txt2Len; i++) {
var a = angle - (i * 360 / txt2Len);
$(txt2[i]).css({top: r+(Math.sin(deg2rad(a))*r), left:
r+(Math.cos(deg2rad(a))*r)});
}
// increment our angle and use a modulo so we are always in the range [0..360] degrees
angle = (angle - speed) % 360;
// after a slight delay, call the exact same function again
setTimeout(rotate, delay);
})(); // invoke this boxed function to initiate the rotation
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();

Categories