<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div onmouseover="animatedStickers($(this), 17, 5, 4, 2, 3)" style="float: left; background-image: url('http://googledrive.com/host/0B-UH4_eX_YisdlJ4cU9qZ1lwM3c/Tuzki1.png'); background-size: 380px 304px; cursor: pointer; height: 64px; width: 64px; background-position: -6px -6px; color: transparent">Tuzki</div>
<div onmouseover="animatedStickers($(this), 16, 4, 4, 4, 3)" style="float: left; background-image: url('http://googledrive.com/host/0B-UH4_eX_YisdlJ4cU9qZ1lwM3c/Tuzki2.png'); background-size: 304px 304px; cursor: pointer; height: 64px; width: 64px; background-position: -6px -6px; color: transparent">Tuzki</div>
<div onmouseover="animatedStickers($(this), 22, 5, 5, 2, 3)" style="float: left; background-image: url('http://googledrive.com/host/0B-UH4_eX_YisdlJ4cU9qZ1lwM3c/Tuzki3.png'); background-size: 380px 380px; cursor: pointer; height: 64px; width: 64px; background-position: -6px -6px; color: transparent">Tuzki</div>
<script>
var loop = 1;
var stickerInterval = function (element, x, y, last) {
var pos = $(element).css('backgroundPosition').split(' ');
var xPos = parseInt(pos[0].split('px')[0]) - 6 - 6 - 64;
var yPos = parseInt(pos[1].split('px')[0]);
var maxX = ((-6 - 6 - 64) * (x - 1)) - 6;
var maxY = ((-6 - 6 - 64) * last) - 6;
if (loop == y && xPos == maxY) {
// end 1 turn
loop = 1;
xPos = -6;
yPos = -6;
} else if (loop < y && xPos < maxX) {
xPos = -6;
yPos = yPos -6 -6 -64;
loop++;
}
$(element).css('background-position', xPos + 'px ' + yPos + 'px');
};
var animatedStickers = function (element, total, x, y, last, times) {
$(element).removeAttr('onmouseover');
var interval = setInterval(function () { stickerInterval(element, x, y, last) }, 175);
setTimeout(function () {
clearInterval(interval);
loop = 1;
$(element).css('background-position', '-6px -6px');
$(element).attr('onmouseover', 'animatedStickers($(this), ' + total + ', ' + x + ', ' + y + ', ' + last + ', ' + times + ')')
}, 175 * total * times);
};
</script>
I wanna use multiple setInterval() and clear all of them NOT in a time.
<div id="div1" onmouseover="divOver($(this))"></div>
<div id="div2" onmouseover="divOver($(this))"></div>
<script>
var divOver = function (element) {
var id = $(element).attr('id'); // get id
//call setInterval() without the id
var interval = setInterval(function(){ /* do something... */ }, 500);
//clear interval after 1s
setTimeout(function(){ clearInterval(interval) }, 1000);
};
</script>
That code doesn't work fine if I mouseover 2 divs at the same time.
I think: The first I mouseover on div1, function divOver creates a variable name interval. After that (haven't cleared interval yet), I mouseover on div2, function divOver comtinues creating a new variable with the same name interval. So, the first interval can be overridden. Is it right?
To avoid that problem, I think about using setInterval() with id. Something's like this:
var id = $(element).attr('id');
//var interval_ + id = setInterval(function(){ /* do something... */ }, 500);
But that's not javascript syntax. Can you give me any idea to fix this problem?
To answer your question how to maintain a record of different intervals at the same time and being able to start and stop them outside the function scope.
You need to keep an associative arrays with the intervals, such that there can be many intervals at the same time.
<div id="div1" onmouseover="divOver($(this))"></div>
<div id="div2" onmouseover="divOver($(this))"></div>
<script>
var intervals = []
var divOver = function (element) {
var id = element.attr('id'); // get id
//call setInterval() with the id
intervals['i'+id] = setInterval(function(){ /* do something... */ }, 500);
//clear interval after 1s
setTimeout(function(){ clearInterval(intervals['i'+id]) }, 1000);
};
</script>
Though as already mentioned this does most likely not solve your real problem.
Related
I have a task to make animation with JavaScript.
Basically I have two squares (red and yellow) and a two buttons (button 1 and button 2).
When I click on button1 the red square goes from the (top-left corner) to the (bottom-right corner).
I need to make another button (button2) such that when I click on it I need the red square to go back to the beginning.
I need it to do the opposite move (moving from the bottom-right corner to the top-left corner).
What changes should I do in the second function?
here is the code
function myMove1() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function myMove2() {
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
I'm going to assume the teacher is trying to teach basic javascript, and tell you how I'd solve this with the parts you've provided.
That said, your commenters are correct, requestAnimationFrame is the right tool here. Also, the 5 ms delay on your interval is really short (125fps). If you made this number, I'd suggest changing it to 16, which is roughly 60fps.
// We want each function to be able to see these vars.
var pos = 0;
// Either -1, 0, or 1, depending on if were moving forward, backwards or
// stopped.
var direction = 0;
// This var now serves dual purpose, either its a number which is the
// interval id or its falsy, which we can use to understand the animation
// has stopped.
var id = null;
// Doing this here, will save the browser from having to redo this step on
// each frame.
var elem = document.getElementById("animate");
// Render the elem to the correct starting location.
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
// A single animation function.
function frame() {
// Assume we are heading for 350.
var goal = 350
if (direction < 0) {
// unless the goal is -1, when the goal is zero.
goal = 0
}
if (pos != goal) {
pos += direction;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
} else {
// Reset all the shared vars.
direction = 0;
clearInterval(id);
id = null;
}
}
function myMove1() {
if (id) {
clearInterval(id)
}
direction = 1;
id = setInterval(frame, 5);
}
function myMove2() {
if (id) {
clearInterval(id)
}
direction = -1;
id = setInterval(frame, 5);
}
#animate {
position: absolute;
width: 10px;
height: 10px;
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
</body>
</html>
What you're asking is straightforward: take the function you already wrote and change the increment direction on pos. The only difference is you'll need to keep track of x and y coordinates separately since they move in opposite directions. I used this object initialized to the start position of the box:
pos = {x: 350, y: 0};
function myMove1() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function myMove2() {
var elem = document.getElementById("animate");
var pos = {x: 350, y: 0};
var id = setInterval(frame, 5);
function frame() {
if (pos.y >= 350 || pos.x <= 0) {
clearInterval(id);
} else {
pos.x--;
pos.y++;
elem.style.top = pos.y + 'px';
elem.style.left = pos.x + 'px';
}
}
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
However, these functions aren't reusable without parameters; this code is WET (wrote everything twice). The animation is brittle because each click creates a new timeout (you can spam the buttons and watch it crumble). Entities in the animation have no state. If you want to change the position or add another box, you have to we-write and duplicate all of your code again.
With that in mind, here's a sketch to illustrate a somewhat improved version as food for thought. The functions and objects are more general and don't need to be re-written for new movements you decide to add. The Box class keeps track of entity state over time. requestAnimationFrame() is used to update and draw all entities on the screen at once, avoiding the many problems with setTimeout.
const lerp = (v0, v1, t) => (1 - t) * v0 + t * v1;
const dist = (a, b) => ((a.x - b.x) ** 2 + (a.y - b.y) ** 2) ** 0.5;
class Box {
constructor(elem, pos, size, color, speed) {
this.elem = elem;
this.speed = speed;
this.from = this.to = this.pos = pos;
this.t = 0;
this.elem.style.position = "absolute";
this.elem.style.background = color;
this.elem.style.height = `${size}px`;
this.elem.style.width = `${size}px`;
this.elem.style.top = `${this.pos.y}px`;
this.elem.style.left = `${this.pos.x}px`;
}
move(to) {
this.from = {x: this.pos.x, y: this.pos.y};
this.to = {x: to.x, y: to.y};
this.t = 0;
}
update() {
if (dist(this.pos, this.to) > 1) {
this.pos.x = lerp(this.from.x, this.to.x, this.t);
this.pos.y = lerp(this.from.y, this.to.y, this.t);
this.elem.style.top = `${this.pos.y}px`;
this.elem.style.left = `${this.pos.x}px`;
this.t += this.speed;
}
}
}
const data = [
{color: "red", pos: {x: 0, y: 0}, size: 10},
{color: "yellow", pos: {x: 350, y: 0}, size: 10},
];
const elems = document.getElementsByClassName("box");
const boxes = [];
for (let i = 0; i < elems.length; i++) {
boxes.push(new Box(elems[i], data[i].pos, data[i].size, data[i].color, 0.01));
}
function myMove1() {
boxes[0].move({x: 350, y: 350});
boxes[1].move({x: 0, y: 350});
}
function myMove2() {
boxes[0].move({x: 0, y: 0});
boxes[1].move({x: 350, y: 0});
}
(function render() {
boxes.forEach(e => e.update());
requestAnimationFrame(render);
})();
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div class="box"></div>
<div class="box"></div>
</div>
Lastly, consider using CSS animations, JS canvas or an animation framework to do this sort of task; these tools will abstract away a lot of the math and state representation that animations involve.
How can i set the duration of an transition/animation to pixel per second?
You see the two different wrappers, with a different total height depending on it's colored content. The total speed is the same, given from the css transition attribute, thats okay if you want several animations with the same duration. For a smoother look i want to set this transition/animation effect to pixel per second so it takes as long as many pixels there. More content = more pixel = longer animation.
How can i achieve this with vanilla javascript or even css?
var wrapper1 = document.getElementById('wrapper1');
var wrapper2 = document.getElementById('wrapper2');
var header1 = document.getElementById('header1');
var header2 = document.getElementById('header2');
var wrapper1CmputedHeight = wrapper1.scrollHeight;
var wrapper2CmputedHeight = wrapper2.scrollHeight;
header1.addEventListener('click', function() {
if (wrapper1.style.height === '60px') {
wrapper1.style.height = wrapper1CmputedHeight + 'px';
} else {
wrapper1.style.height = '60px';
}
})
header2.addEventListener('click', function() {
if (wrapper2.style.height === '60px') {
wrapper2.style.height = wrapper2CmputedHeight + 'px';
} else {
wrapper2.style.height = '60px';
}
})
#wrapper1,
#wrapper2 {
background: #fff;
border: 1px solid grey;
overflow: hidden;
transition: height .2s linear
}
#wrapper1 {
margin-bottom: 40px
}
#header1,
#header2 {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer
}
#content1 {
height: 20px;
background: blue
}
#content2 {
height: 600px;
background: green
}
<div id="wrapper1" style="height: 60px">
<div id="header1">
<span>header</span>
</div>
<div id="content1"></div>
</div>
<div id="wrapper2" style="height: 60px">
<div id="header2">
<span>header</span>
</div>
<div id="content2"></div>
</div>
The only way to do this with css transitions, is to dynamically calculate the duration of the transition using a little javascript. So, in your code, I would remove the duration for the transition rule in your css, i,e.
#wrapper1,
#wrapper2 {
background: #fff;
overflow: hidden;
transition: height linear
}
and I would instead set the duration in the click handler as follows:
header1.addEventListener('click', function () {
if(wrapper1.style.height === '60px') {
wrapper1.style.height = wrapper1CmputedHeight + 'px';
wrapper1.style.transitionDuration=(wrapper1CmputedHeight/100)+"s";
} else {
wrapper1.style.height = '60px';
}
})
So in this case, I've used a speed of 100px per second (this is the /100 part in the above code).
I found this example here but it seems to do the trick for you (after some tweaking). In this case it implements a quartic interpolation, however you could adjust this algorithm to linear / other if so desired.
//
// Animate
//
var btn1 = document.querySelector('.animate');
btn1.addEventListener('click', function() {
reset();
animate();
btn1.disabled = true;
});
//
// http://easings.net/#easeInOutQuart
// t: current time
// b: beginning value
// c: change in value
// d: duration
//
function easeInOutQuart(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
function reset() {
document.querySelector('.square').style.width = Math.floor((Math.random() * 500) + 1) + "px";
}
function animate() {
var rect = document.querySelector('.square');
var from = 0;
var to = window.getComputedStyle(rect, null).getPropertyValue("width").split('px')[0];
var duration = to * 10;
var start = new Date().getTime();
var timer = setInterval(function() {
var time = new Date().getTime() - start;
var width = easeInOutQuart(time, from, to - from, duration);
rect.style.width = width + "px";
if (time >= duration) {
clearInterval(timer);
btn1.disabled = false;
}
}, 1000 / 60);
rect.style.width = from;
}
reset();
.square {
position: relative;
display: block;
width: 30px;
height: 30px;
background-color: #f00;
}
<div class="square"></div>
<button class="animate">Animate</button>
I have a wrapper called #mousearea and I have a div called #mouseshift what I would like to do is when I hover over #mousearea I would like to shift the translate3d(0,230%,0) value between a particular range.
I have got the mousemove working but I currently end up with something like translate3d(7881%,230%,0) it's just too sensetive I would like it to translate the X co-ordinate between something like 0-60% so it's far more subtle.
Here is what I have so far:
jQuery(document).ready(function($){
$('#mousearea').mousemove(function (e) {
var shiftAmount = 1;
$('#mouseshift').css(
'transform', 'rotate(90deg) translate3d(' + -e.pageY + shiftAmount + '%,230%,0)'
);
});
});
Update:
This is a little closer, except it logs the correct translate3d but doesn't apply it to #mouseshift.
$('#mousearea').mousemove(function(e){
var x = e.pageY - this.offsetTop;
var transfromPosition = 'translate3d(' + x + ', 230%, 0)';
console.log(transfromPosition);
if ((x <= 800)) {
//$('#mouseshift').css({'top': x});
$('#mouseshift').css('transform', transfromPosition);
}
});
Final Solution:
jQuery(document).ready(function($){
$('#mousearea').mousemove(function(e){
var min = 50;
var max = 70;
var x = e.pageY;
var windowHeight = window.innerHeight;
scrolled = (x / windowHeight);
percentageScrolled = scrolled * 100;
offsetScroll = max - min;
offsetPercentage = scrolled * 20;
translateX = min + offsetPercentage;
console.log(x + 'px');
console.log(windowHeight + 'px window height');
console.log(percentageScrolled + '% scrolled');
console.log(offsetScroll + 'offset scroll');
console.log(offsetPercentage + '% offset percentage');
var transfromPosition = 'rotate(90deg) translate3d(' + translateX + '%, 230%, 0)';
$('#mouseshift h1').css('transform', transfromPosition);
});
});
Convert to a reusable plugin I would like to extend this to work with more than one object now and each object would have a different max and min value:
This is what I have but it seems to effect all the items on only use on elements max and min.
$(function () {
$('#mouseshift-1, #mouseshift-2').mouseShift();
});
(function ($) {
$.fn.mouseShift = function () {
return this.each(function () {
var myEl = $(this);
var min = $(this).data('min');
var max = $(this).data('max');
$('#mousearea').mousemove(function (e) {
var yPosition = e.pageY;
var windowHeight = window.innerHeight;
scrolled = (yPosition / windowHeight);
//percentageScrolled = scrolled * 100;
offsetRange = max - min;
offsetRangePercentage = scrolled * 20;
offset = min + offsetRangePercentage;
//// Debug
console.log('max: ' + max + ', Min:' + min);
console.log(yPosition + 'px');
console.log(windowHeight + 'px window height');
//console.log(percentageScrolled + '% scrolled');
console.log(offsetRange + 'px offset scroll');
console.log(offsetRangePercentage + '% offset percentage');
var transfromPosition = 'rotate(90deg) translate3d(' + offset + '%, 230%, 0)';
myEl.css('transform', transfromPosition);
});
});
};
})(jQuery);
And some HTML for clarity:
<div class="column"><h1 id="mouseshift-1" data-min="50" data-max="70">boo</h1></div>
<div class="column"><h1 id="mouseshift-2" data-min="20" data-max="90">bah</h1></div>
<div class="column"><h1 id="mouseshift-3" data-min="80" data-max="100">bing</h1></div>
I think what you are looking for is finding an average that your can distribute. The best way to do this is to divide by the maximum amount it can move, and multiply it by the maximum value it can have, so basically:
position / maxposition * maxvalue
The first bit will return a number between 0 and 1, while the last bit will make it the value between 0 and 60. Below I have built a simply (jquery-less) version of it to show how this would work:
var mousePointer = document.getElementById('test')
document.addEventListener('mousemove', function(e){
var x = e.pageX / window.innerHeight;
x = x * -60;
mousePointer.style.webkitTransform = 'translateX(' + x + '%)';
mousePointer.style.transform = 'translateX(' + x + '%)';
})
#test {
position: absolute;
left: 50%;
top: 50%;
width: 20px;
height: 20px;
background: red;
}
<div id="test"></div>
Update: Reusable Snippet
I don't really like using jQuery, so once again it will be vanilla javascript (but it's pretty simple). Is that what you were - sort of - trying to do with the reusable plugin?
var divs = Array.prototype.slice.call(document.querySelectorAll('[data-range]'));
document.addEventListener('mousemove', function(e){
var eased = e.pageX / window.innerWidth;
divs.forEach(function(div){
var range = div.getAttribute('data-range').split(',');
var min = parseFloat(range[0]);
var max = parseFloat(range[1]);
var ease = min + (eased * (max - min));
div.style.webkitTransform = 'translateX(' + ease + '%)';
div.style.transform = 'translateX(' + ease + '%)';
});
});
div {
position: absolute;
left: 50%;
top: 50%;
width: 200px;
height: 200px;
background: gray;
}
#d2 { background: yellow; }
#d3 { background: #666; }
<div data-range="60,70" id="d1"></div>
<div data-range="-70,70" id="d2"></div>
<div data-range="-60,-70" id="d3"></div>
From simple reading, I see that you're missing a % sign. Should be like this:
$('#mousearea').mousemove(function(e){
var x = e.pageY - this.offsetTop;
var transfromPosition = 'translate3d(' + x + '%, 230%, 0)';
console.log(transfromPosition);
if ((x <= 800)) {
//$('#mouseshift').css({'top': x});
$('#mouseshift').css('transform', transfromPosition);
}
});
This should be working like your first example, where you do use % for both values inside the translate3d string.
Update:
To coerce your x Value to something between 0 and 60, you need to find a pair of possible min and max values for x. Then you can do something like what's shown in this answer:
Convert a number range to another range, maintaining ratio
is there a way to get the touch position in a touchmove event every x milliseconds and then execute a function, when the x-coordinate at the moment and the one at the start are differing e.g. 50px?
Thanks
Try the below ;
$('document').ready(function() {
var touch,
action,
diffX,
diffY,
endX,
endY,
startX,
startY,
timer,
timerXseconds = 500, // Change to the Time(milliseconds) to check for touch position
xDifferenceX = 50, // Change to difference (px) for x-coordinates from starting point to run your function
xDifferenceY = 50; // Change to difference (px) for y-coordinates from starting point
function getCoord(e, c) {
return /touch/.test(e.type) ? (e.originalEvent || e).changedTouches[0]['page' + c] : e['page' + c];
}
function testTouch(e) {
if (e.type == 'touchstart') {
touch = true;
} else if (touch) {
touch = false;
return false;
}
return true;
}
function onStart(ev) {
if (testTouch(ev) && !action) {
action = true;
startX = getCoord(ev, 'X');
startY = getCoord(ev, 'Y');
diffX = 0;
diffY = 0;
timer = window.setInterval(checkPosition(ev), timerXseconds); // get coordinaties ever X time
if (ev.type == 'mousedown') {
$(document).on('mousemove', onMove).on('mouseup', onEnd);
}
}
}
function onMove(ev) {
if (action) {
checkPosition(ev)
}
}
function checkPosition(ev) {
endX = getCoord(ev, 'X');
endY = getCoord(ev, 'Y');
diffX = endX - startX;
diffY = endY - startY;
// Check if coordinates on Move are Different than Starting point by X pixels
if (Math.abs(diffX) > xDifferenceX || Math.abs(diffY) > xDifferenceY) {
// console.log('Start is :' + startX + ' End is : ' + endX + 'Difference is : ' + diffX);
$(this).trigger('touchend');
// here Add your function to run...
}
}
function onEnd(ev) {
window.clearInterval(timer);
if (action) {
action = false;
if (ev.type == 'mouseup') {
$(document).off('mousemove', onMove).off('mouseup', onEnd);
}
}
}
$('#monitor')
.bind('touchstart mousedown', onStart)
.bind('touchmove', onMove)
.bind('touchend touchcancel', onEnd);
});
body {
margin: 0;
padding: 0;
}
#monitor {
height: 500px;
width: 500px;
position: relative;
display: block;
left: 50px;
top: 50px;
background: green;
}
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: center;
font-weight: bold;
bottom: 0;
background: white;
width: 50px;
height: 50px;
margin: auto;
font-size: 16px;
line-height: 23px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='monitor'>
<div class='box'>start here</div>
</div>
Read this post for a more detailed answer
This can be done in a few functions.
The first function is called when there is a movement of the touch event, this event stores the x and y of the touch in a separate variable.
Then we have a function that runs every X miliseconds, this function gets the x and y from the move event and dispatches then to your code.
Functions 3, 4 and 5 are used to handle the start, stop and cancel dragevents, and start/stop the second function:
var timerid;
var x;
var y;
var tick = 0;
function handleStart(evt) {
console.log("handleStart");
evt.preventDefault();
timerid = window.setInterval(timer, 50); // Replace 50 here with X
}
function handleEnd(evt) {
console.log("handleEnd");
evt.preventDefault();
window.clearInterval(timerid);
}
function handleCancel(evt) {
console.log("handleCancel");
evt.preventDefault();
window.clearInterval(timerid);
}
function handleMove(evt) {
console.log("handleMove");
evt.preventDefault();
// Select last point:
var point = evt.changedTouches[evt.changedTouches.length - 1];
x = point.pageX;
y = point.pageY;
}
function timer() {
console.log("timer");
tick++;
document.getElementById("output").innerHTML = "tick: " + tick + " x: " + x + " y:" + y;
}
var el = document.getElementById("canvas");
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchcancel", handleCancel, false);
el.addEventListener("touchmove", handleMove, false);
<canvas id="canvas" width="300" height="300" style="border:solid black 1px;"></canvas>
<p id=output></p>
As long as the user is pressing the screen, the code will print out the x and the y coordinate to the screen. You can also integrate the reading of the x and y into your existing game loop instead of having a separate function if that is needed for your project.
Take a look at hammer.js, it has exactly what you need. It supports "touchmove" called pan, that is being called every few milliseconds when you pan. Also there is a threshold property which determine a length in pixels you have to pan before recognizing it as a pan.
I am having trouble trying to implement a "prize wheel," using canvas. I am using something similar to the canvas "roulette wheel" http://jsfiddle.net/wYhvB/4/ that is floating around StackOverflow. My dilema is when you click spin, in the background I make an API call that returns an id of which prize should actually be chosen, the interface is nothing more than eye candy. I am pushing all the prize descriptions into the first array, how can I add an id into each one of the arcs and stop on a particular one instead of stopping on a specifically random time? I.E. If the API returns "car," I want this wheel to spin a few times and stop on car.
var colors = ["##eaeaea", "##cccccc", "##eaeaea", "##cccccc",
"##eaeaea", "##cccccc", "##eaeaea", "##cccccc"];
// NEED to pre load this data prior
var prize_descriptions = ["car","house","etc..."]; // These are injected on an init call from an api
var current_user_status = {};
var startAngle = 0;
var arc = Math.PI / 4;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var current_user_status = null;
var spin_results = null;
var ctx;
function drawSpinnerWheel() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var outsideRadius = 200;
var textRadius = 160;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 12px Helvetica, Arial';
for(var i = 0; i < 8; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
ctx.fillStyle = "black";
ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius,
250 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = prize_descriptions[i];
if (text == undefined)
text = "Not this time! "+i;
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
ctx.fill();
}
}
function spin() {
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * 3 + 4 * 1000;
rotateWheel();
}
function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawSpinnerWheel();
spinTimeout = setTimeout('rotateWheel()', 30);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
var degrees = startAngle * 180 / Math.PI + 90;
var arcd = arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
ctx.save();
ctx.font = 'bold 30px Helvetica, Arial';
var text = prize_descriptions[index];
ctx.fillText(text, 250 - ctx.measureText(text).width / 2, 250 + 10);
ctx.restore();
}
function easeOut(t, b, c, d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
drawSpinnerWheel();
$("#spin").bind('click', function(e) {
e.preventDefault();
spin();
});
I have some experience in creating HTML5 canvas winning wheels, the way that I solved the problem of getting the wheel to stop at at a particular prize determined by a server-side process was to define an array of prizes that correspond to what is shown on the wheel, specifying the start and end angle of each prize in degrees, and then before the wheel is rotated setting a targetAngle to a random value between the start and end angle of the prize in question plus adding a multiple of 360 degrees so that the wheel spins a few times before slowing to a stop at the predetermined prize.
For example if the wheel has 4 prizes then the prizes array is...
var prizes = new Array();
prizes[0] = {"name" : "Prize 1", "startAngle" : 0, "endAngle" : 89};
prizes[1] = {"name" : "Prize 2", "startAngle" : 90, "endAngle" : 179};
prizes[2] = {"name" : "Prize 3", "startAngle" : 180, "endAngle" : 269};
prizes[3] = {"name" : "Prize 4", "startAngle" : 270, "endAngle" : 359};
And the code to set the targetAngle is something like...
targetAngle = Math.floor(prizes[determinedPrize]['startAngle'] + (Math.random() * (prizes[determinedPrize]['endAngle'] - prizes[determinedPrize]['startAngle'])));
targetAngle += (360 * 18);
The spinning function of the wheel then loops until the current angle of the wheel equals the targetAngle.
A working example of my prize wheel and the fully commented source code is available at http://www.dougtesting.net. The pre-determined feature is not enabled in the online example, but can easily be turned on in the source code (winwheel.js) once downloaded.
The easiest way I can think of is to take the current position of the wheel, then calculate the distance from this point to the prize. Add a random number of multiples of the wheel diameter circumference and then you have a distance. The edge of the wheel must travel through this distance to end up at the prize.
Just like you can use linear or cubic interpolation to move an element from 1 position to another in a specified number of steps, you can use the same approach to rotate the wheel from point 0 (start point) to point 1 (end point) from time=0 to time=1
This page Math: Ease In, ease Out a displacement using Hermite curve with time constraint is a good read. It's where I managed to wrap my head around doing basically the same thing - just up/down/left/right, rather than rotationally.
It's a bit choppy while im look at iot just now. Dont know if it's jsfiddle, the missing images or the 25 browser tabs & programs I have running.
Anyway, the point is to use non-linear interpolation to get to a specified distance away in a specified number of steps. It should get there in a specified time, but not with 25 windows open.. :laughs:
Check out the SO link above. It's got some great pictures that really explain quite well.
Here's a fiddle of cubic-spline interpolation for the time.
http://jsfiddle.net/enhzflep/XKzGF/
And here's the full code:
<!DOCTYPE html>
<html>
<head>
<script>
var continuePlaying = true, isPlaying=false;
function byId(a){return document.getElementById(a)}
function myInit()
{
}
window.addEventListener("load",myInit,!1);
function cubicHermite(a,b,d,e,c){var g=a*a,f=g*a;return(2*f-3*g+1)*b+(f-2*g+a)*e+(-2*f+3*g)*d+(f-g)*c}
function interp(a,b,d,e,c){var g,f;f=e/(a/2+b+d/2);g=f*a/2;f*=b;return result=c<=a?cubicHermite(c/a,0,g,0,f/b*a):c<=a+b?g+f*(c-a)/b:cubicHermite((c-a-b)/d,g+f,e,f/b*d,0)}
function linear(a){return a}
function cubic(a){return interp(0.35,0.3,0.35,1,a)}
function getSize(a){return{left:a.offsetLeft,top:a.offsetTop,width:a.clientWidth,height:a.clientHeight}}
function doAnim2(a,b,d,e){var c=a/b;setTimeout(function(){doAnimStep(0,b,c,d,e)},c)}
function doAnimStep(a,b,d,e,c){a<=b?(setTimeout(function(){doAnimStep(a,b,d,e,c)},d),e(a/b),a++):void 0!=c&&null!=c&&c()}
//scroll with cubic interpolation of the current scroll position
function cubicScrollDown(b,callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(500,c,function(b){a.scrollTop=c*cubic(b)},callback);
}
function cubicScrollUp(b,callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(500,c,function(b){ a.scrollTop=c*(1-cubic(b)) },callback );
}
//scroll with cubic interpolation of the current scroll position
function linearScrollDown(b, callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(500,c,function(b){a.scrollTop=c*linear(b)}, callback);
}
function linearScrollUp(b, callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(1000,c,function(b){ a.scrollTop=c*(1-linear(b)) }, callback );
}
function animFadeOut(elem, callback)
{
doAnim2(500,50,function(raw){elem.style.opacity=1-cubic(raw)},callback);
}
function animFadeIn(elem, callback)
{
doAnim2(500,50,function(raw){elem.style.opacity=cubic(raw)},callback);
}
function cubicBounce(b)
{
cubicScrollDown(b, downCallback);
function downCallback()
{
cubicScrollUp(b, upCallback);
}
function upCallback()
{
if (continuePlaying===true)
setTimeout( function(){cubicBounce(b);}, 0);
else
continuePlaying = true;
}
}
function linearBounce(b)
{
linearScrollDown(b, downCallback);
function downCallback()
{
linearScrollUp(b, upCallback);
}
function upCallback()
{
if (continuePlaying===true)
setTimeout( function(){linearBounce(b);}, 0);
else
continuePlaying = true;
}
}
function fadeOutIn(tgtListIdStr)
{
var tgt = byId(tgtListIdStr);
animFadeOut(tgt,fadedOutCallback);
function fadedOutCallback()
{
animFadeIn(tgt);
}
}
function prependChild(parent, element)
{
if (parent.childNodes)
parent.insertBefore(element, parent.childNodes[0]);
else
parent.appendChild(element)
}
function slideUpRemove(tgtListIdStr)
{
var tgt = byId(tgtListIdStr);
var listItems = tgt.getElementsByTagName('li');
mHeight = listItems[0].clientHeight;
animFadeOut(listItems[0], slideUp);
function slideUp()
{
doAnim2(500, 50, slideUpStep, slideUpDone);
function slideUpStep(raw)
{
listItems[0].style.height = (cubic(1-raw) * mHeight) + 'px';
}
function slideUpDone()
{
dummy = listItems[0];
tgt.appendChild(dummy);
//dummy.removeAttribute('style');
dummy.style.height = null;
dummy.style.opacity = null;
}
}
}
function slideDownInsert(tgtListIdStr)
{
// get the container, it's items and the height of the last LI item.
var tgt = byId(tgtListIdStr);
var listItems = tgt.getElementsByTagName('li');
mHeight = listItems[listItems.length-1].clientHeight;
// create a dummy to take the place of the last item, set it's size and height. make it the first child of the containing list
var dummy = document.createElement('li');
dummy.style.opacity = 0;
dummy.style.height = 0 + 'px';
prependChild(tgt, dummy);
// animate it!
doAnim2(500, 50, slideDownStep,slideDownDone);
function slideDownStep(raw)
{
dummy.style.height = (cubic(raw) * mHeight)+'px';
}
function slideDownDone()
{
// remove the dummy
var newItem = listItems[listItems.length-1];
newItem.style.opacity = 0;
prependChild(tgt, newItem);
tgt.removeChild(dummy);
animFadeIn(newItem, function(){newItem.removeAttribute('style')});
}
}
</script>
<style>
#myListDiv
{
width: 256px;
padding: 6px;
height: 128px;
overflow-y: hidden; /*scroll;*/
border-radius: 6px;
border: solid 1px transparent;
border-color: rgba(0,0,0,0.2) rgba(255,255,255,0.4) rgba(255,255,255,0.4) rgba(0,0,0,0.2);
/* background-image: url(img/rss128.png); */
background-color: rgba(0,0,0,0.1);
}
h4, p
{
margin: 6px 0;
}
ul
{
padding: 0;
list-style: none;
margin: 0;
}
ul#mList li
{
padding: 0 8px;
margin: 0 6px;
display: block;
border: solid 1px #cccccc;
border-bottom-color: #000;
border-color: #ccc transparent #000 transparent;
vertical-align: middle;
background-color: rgba(150,150,150,0.95);
overflow: hidden;
}
.thumb
{
width: 48px;
height: 48px;
float: left;
}
.thumb img
{
height: 48px;
}
#mPanel
{
display: inline-block;
float: left;
padding: 32px;
background-color: hsl(80,50%,20%);
}
</style>
</head>
<body>
<div id='mPanel'>
<div id='myListDiv'>
<ul id='mList'>
<li><div class='thumb'><img src='img/opera.svg'></div><div class='itemData'><h4><a>Item #1</a></h4><p>some assorted text</p></div></li>
<li><div class='thumb'><img src='img/chromeEyes.svg'></div><h4><a>Item #2</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/girl.png'></div><h4><a>Item #3</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/chuck-norris.jpg'></div><h4><a>Item #1</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/redBaron.jpg'></div><h4><a>Item #2</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/default.png'></div><h4><a>Item #3</a></h4><p>some assorted text</p></li>
</ul>
</div>
</div>
<button onclick='cubicScrollDown("myListDiv")'>Cubic down</button>
<button onclick='cubicScrollUp("myListDiv")'>Cubic up</button><br>
<button onclick='cubicBounce("myListDiv")'>cubic bounce</button>
<button onclick='linearBounce("myListDiv")'>linear bounce</button><br>
<input type='button' onclick='slideUpRemove("mList")' value='newest'/>
<input type='button' onclick='slideDownInsert("mList")' value='Slide Down'/><br>
<button onclick='continuePlaying=false'>Stop Anim cycle</button>
<input type='button' onclick='fadeOutIn("mList");' value='fadeOutIn'/><br>
</body>
</html>