This is the large js, not the min one that the demo uses but it’ll be easier to see lol.
/**
*
* Find more about the Spinning Wheel function at
* http://cubiq.org/spinning-wheel-on-webkit-for-iphone-ipod-touch/11
*
* Copyright (c) 2009 Matteo Spinelli, http://cubiq.org/
* Released under MIT license
* http://cubiq.org/dropbox/mit-license.txt
*
* Version 1.4 - Last updated: 2009.07.09
*
*/
var SpinningWheel = {
cellHeight: 44,
friction: 0.003,
slotData: [],
/**
*
* Event handler
*
*/
handleEvent: function (e) {
if (e.type == 'touchstart') {
this.lockScreen(e);
if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
this.tapDown(e);
} else if (e.currentTarget.id == 'sw-frame') {
this.scrollStart(e);
}
} else if (e.type == 'touchmove') {
this.lockScreen(e);
if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
this.tapCancel(e);
} else if (e.currentTarget.id == 'sw-frame') {
this.scrollMove(e);
}
} else if (e.type == 'touchend') {
if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
this.tapUp(e);
} else if (e.currentTarget.id == 'sw-frame') {
this.scrollEnd(e);
}
} else if (e.type == 'webkitTransitionEnd') {
if (e.target.id == 'sw-wrapper') {
this.destroy();
} else {
this.backWithinBoundaries(e);
}
} else if (e.type == 'orientationchange') {
this.onOrientationChange(e);
} else if (e.type == 'scroll') {
this.onScroll(e);
}
},
/**
*
* Global events
*
*/
onOrientationChange: function (e) {
window.scrollTo(0, 0);
this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
this.calculateSlotsWidth();
},
onScroll: function (e) {
this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
},
lockScreen: function (e) {
e.preventDefault();
e.stopPropagation();
},
/**
*
* Initialization
*
*/
reset: function () {
this.slotEl = [];
this.activeSlot = null;
this.swWrapper = undefined;
this.swSlotWrapper = undefined;
this.swSlots = undefined;
this.swFrame = undefined;
},
calculateSlotsWidth: function () {
var div = this.swSlots.getElementsByTagName('div');
for (var i = 0; i < div.length; i += 1) {
this.slotEl[i].slotWidth = div[i].offsetWidth;
}
},
create: function () {
var i, l, out, ul, div;
this.reset();// Initialize object variables
// Create the Spinning Wheel main wrapper
div = document.createElement('div');
div.id = 'sw-wrapper';
div.style.top = window.innerHeight + window.pageYOffset + 'px';// Place the SW down the actual viewing screen
div.style.webkitTransitionProperty = '-webkit-transform';
div.innerHTML = '
Cancel
Done
';
document.body.appendChild(div);
this.swWrapper = div;// The SW wrapper
this.swSlotWrapper = document.getElementById('sw-slots-wrapper');// Slots visible area
this.swSlots = document.getElementById('sw-slots');// Pseudo table element (inner wrapper)
this.swFrame = document.getElementById('sw-frame');// The scrolling controller
// Create HTML slot elements
for (l = 0; l < this.slotData.length; l += 1) {
// Create the slot
ul = document.createElement('ul');
out = '';
for (i in this.slotData[l].values) {
out += '
' + this.slotData[l].values[i] + '<' + '/li>';
}
ul.innerHTML = out;
div = document.createElement('div');// Create slot container
div.className = this.slotData[l].style;// Add styles to the container
div.appendChild(ul);
// Append the slot to the wrapper
this.swSlots.appendChild(div);
ul.slotPosition = l;// Save the slot position inside the wrapper
ul.slotYPosition = 0;
ul.slotWidth = 0;
ul.slotMaxScroll = this.swSlotWrapper.clientHeight - ul.clientHeight - 86;
ul.style.webkitTransitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)';// Add default transition
this.slotEl.push(ul);// Save the slot for later use
// Place the slot to its default position (if other than 0)
if (this.slotData[l].defaultValue) {
this.scrollToValue(l, this.slotData[l].defaultValue);
}
}
this.calculateSlotsWidth();
// Global events
document.addEventListener('touchstart', this, false);// Prevent page scrolling
document.addEventListener('touchmove', this, false);// Prevent page scrolling
window.addEventListener('orientationchange', this, true);// Optimize SW on orientation change
window.addEventListener('scroll', this, true);// Reposition SW on page scroll
// Cancel/Done buttons events
document.getElementById('sw-cancel').addEventListener('touchstart', this, false);
document.getElementById('sw-done').addEventListener('touchstart', this, false);
// Add scrolling to the slots
this.swFrame.addEventListener('touchstart', this, false);
},
open: function () {
this.create();
this.swWrapper.style.webkitTransitionTimingFunction = 'ease-out';
this.swWrapper.style.webkitTransitionDuration = '400ms';
this.swWrapper.style.webkitTransform = 'translate3d(0, -260px, 0)';
},
/**
*
* Unload
*
*/
destroy: function () {
this.swWrapper.removeEventListener('webkitTransitionEnd', this, false);
this.swFrame.removeEventListener('touchstart', this, false);
document.getElementById('sw-cancel').removeEventListener('touchstart', this, false);
document.getElementById('sw-done').removeEventListener('touchstart', this, false);
document.removeEventListener('touchstart', this, false);
document.removeEventListener('touchmove', this, false);
window.removeEventListener('orientationchange', this, true);
window.removeEventListener('scroll', this, true);
this.slotData = [];
this.cancelAction = function () {
return false;
};
this.cancelDone = function () {
return true;
};
this.reset();
document.body.removeChild(document.getElementById('sw-wrapper'));
},
close: function () {
this.swWrapper.style.webkitTransitionTimingFunction = 'ease-in';
this.swWrapper.style.webkitTransitionDuration = '400ms';
this.swWrapper.style.webkitTransform = 'translate3d(0, 0, 0)';
this.swWrapper.addEventListener('webkitTransitionEnd', this, false);
},
/**
*
* Generic methods
*
*/
addSlot: function (values, style, defaultValue) {
if (!style) {
style = '';
}
style = style.split(' ');
for (var i = 0; i < style.length; i += 1) {
style[i] = 'sw-' + style[i];
}
style = style.join(' ');
var obj = { 'values': values, 'style': style, 'defaultValue': defaultValue };
this.slotData.push(obj);
},
getSelectedValues: function () {
var index, count,
i, l,
keys = [], values = [];
for (i in this.slotEl) {
// Remove any residual animation
this.slotEl[i].removeEventListener('webkitTransitionEnd', this, false);
this.slotEl[i].style.webkitTransitionDuration = '0';
if (this.slotEl[i].slotYPosition > 0) {
this.setPosition(i, 0);
} else if (this.slotEl[i].slotYPosition < this.slotEl[i].slotMaxScroll) {
this.setPosition(i, this.slotEl[i].slotMaxScroll);
}
index = -Math.round(this.slotEl[i].slotYPosition / this.cellHeight);
count = 0;
for (l in this.slotData[i].values) {
if (count == index) {
keys.push(l);
values.push(this.slotData[i].values[l]);
break;
}
count += 1;
}
}
return { 'keys': keys, 'values': values };
},
/**
*
* Rolling slots
*
*/
setPosition: function (slot, pos) {
this.slotEl[slot].slotYPosition = pos;
this.slotEl[slot].style.webkitTransform = 'translate3d(0, ' + pos + 'px, 0)';
},
scrollStart: function (e) {
// Find the clicked slot
var xPos = e.targetTouches[0].clientX - this.swSlots.offsetLeft;// Clicked position minus left offset (should be 11px)
// Find tapped slot
var slot = 0;
for (var i = 0; i < this.slotEl.length; i += 1) {
slot += this.slotEl[i].slotWidth;
if (xPos < slot) {
this.activeSlot = i;
break;
}
}
// If slot is readonly do nothing
if (this.slotData[this.activeSlot].style.match('readonly')) {
this.swFrame.removeEventListener('touchmove', this, false);
this.swFrame.removeEventListener('touchend', this, false);
return false;
}
this.slotEl[this.activeSlot].removeEventListener('webkitTransitionEnd', this, false);// Remove transition event (if any)
this.slotEl[this.activeSlot].style.webkitTransitionDuration = '0';// Remove any residual transition
// Stop and hold slot position
var theTransform = window.getComputedStyle(this.slotEl[this.activeSlot]).webkitTransform;
theTransform = new WebKitCSSMatrix(theTransform).m42;
if (theTransform != this.slotEl[this.activeSlot].slotYPosition) {
this.setPosition(this.activeSlot, theTransform);
}
this.startY = e.targetTouches[0].clientY;
this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
this.scrollStartTime = e.timeStamp;
this.swFrame.addEventListener('touchmove', this, false);
this.swFrame.addEventListener('touchend', this, false);
return true;
},
scrollMove: function (e) {
var topDelta = e.targetTouches[0].clientY - this.startY;
if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
topDelta /= 2;
}
this.setPosition(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition + topDelta);
this.startY = e.targetTouches[0].clientY;
// Prevent slingshot effect
if (e.timeStamp - this.scrollStartTime > 80) {
this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
this.scrollStartTime = e.timeStamp;
}
},
scrollEnd: function (e) {
this.swFrame.removeEventListener('touchmove', this, false);
this.swFrame.removeEventListener('touchend', this, false);
// If we are outside of the boundaries, let's go back to the sheepfold
if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
this.scrollTo(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition > 0 ? 0 : this.slotEl[this.activeSlot].slotMaxScroll);
return false;
}
// Lame formula to calculate a fake deceleration
var scrollDistance = this.slotEl[this.activeSlot].slotYPosition - this.scrollStartY;
// The drag session was too short
if (scrollDistance < this.cellHeight / 1.5 && scrollDistance > -this.cellHeight / 1.5) {
if (this.slotEl[this.activeSlot].slotYPosition % this.cellHeight) {
this.scrollTo(this.activeSlot, Math.round(this.slotEl[this.activeSlot].slotYPosition / this.cellHeight) * this.cellHeight, '100ms');
}
return false;
}
var scrollDuration = e.timeStamp - this.scrollStartTime;
var newDuration = (2 * scrollDistance / scrollDuration) / this.friction;
var newScrollDistance = (this.friction / 2) * (newDuration * newDuration);
if (newDuration < 0) {
newDuration = -newDuration;
newScrollDistance = -newScrollDistance;
}
var newPosition = this.slotEl[this.activeSlot].slotYPosition + newScrollDistance;
if (newPosition > 0) {
// Prevent the slot to be dragged outside the visible area (top margin)
newPosition /= 2;
newDuration /= 3;
if (newPosition > this.swSlotWrapper.clientHeight / 4) {
newPosition = this.swSlotWrapper.clientHeight / 4;
}
} else if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
// Prevent the slot to be dragged outside the visible area (bottom margin)
newPosition = (newPosition - this.slotEl[this.activeSlot].slotMaxScroll) / 2 + this.slotEl[this.activeSlot].slotMaxScroll;
newDuration /= 3;
if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4) {
newPosition = this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4;
}
} else {
newPosition = Math.round(newPosition / this.cellHeight) * this.cellHeight;
}
this.scrollTo(this.activeSlot, Math.round(newPosition), Math.round(newDuration) + 'ms');
return true;
},
scrollTo: function (slotNum, dest, runtime) {
this.slotEl[slotNum].style.webkitTransitionDuration = runtime ? runtime : '100ms';
this.setPosition(slotNum, dest ? dest : 0);
// If we are outside of the boundaries go back to the sheepfold
if (this.slotEl[slotNum].slotYPosition > 0 || this.slotEl[slotNum].slotYPosition < this.slotEl[slotNum].slotMaxScroll) {
this.slotEl[slotNum].addEventListener('webkitTransitionEnd', this, false);
}
},
scrollToValue: function (slot, value) {
var yPos, count, i;
this.slotEl[slot].removeEventListener('webkitTransitionEnd', this, false);
this.slotEl[slot].style.webkitTransitionDuration = '0';
count = 0;
for (i in this.slotData[slot].values) {
if (i == value) {
yPos = count * this.cellHeight;
this.setPosition(slot, yPos);
break;
}
count -= 1;
}
},
backWithinBoundaries: function (e) {
e.target.removeEventListener('webkitTransitionEnd', this, false);
this.scrollTo(e.target.slotPosition, e.target.slotYPosition > 0 ? 0 : e.target.slotMaxScroll, '150ms');
return false;
},
/**
*
* Buttons
*
*/
tapDown: function (e) {
e.currentTarget.addEventListener('touchmove', this, false);
e.currentTarget.addEventListener('touchend', this, false);
e.currentTarget.className = 'sw-pressed';
},
tapCancel: function (e) {
e.currentTarget.removeEventListener('touchmove', this, false);
e.currentTarget.removeEventListener('touchend', this, false);
e.currentTarget.className = '';
},
tapUp: function (e) {
this.tapCancel(e);
if (e.currentTarget.id == 'sw-cancel') {
this.cancelAction();
} else {
this.doneAction();
}
this.close();
},
setCancelAction: function (action) {
this.cancelAction = action;
},
setDoneAction: function (action) {
this.doneAction = action;
},
cancelAction: function () {
return false;
},
cancelDone: function () {
return true;
}
};
And this is the untouched demo html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" id="iphone-viewport" content="minimum-scale=1.0, maximum-scale=1.0, width=device-width" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="stylesheet" href="spinningwheel.css" type="text/css" media="all" />
<script type="text/javascript" src="spinningwheel-min.js?v=1.4"></script>
<title>Spinning Wheel for iPhone/iPod touch</title>
<script type="text/javascript">
function openWeight() {
var numbers = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9 };
SpinningWheel.addSlot(numbers, 'right');
SpinningWheel.addSlot(numbers, 'right');
SpinningWheel.addSlot(numbers, 'right');
SpinningWheel.addSlot({ separator: '.' }, 'readonly shrink');
SpinningWheel.addSlot(numbers, 'right');
SpinningWheel.addSlot({ Kg: 'Kg', Lb: 'Lb', St: 'St' }, 'shrink');
SpinningWheel.setCancelAction(cancel);
SpinningWheel.setDoneAction(done);
SpinningWheel.open();
}
function openBirthDate() {
var now = new Date();
var days = { };
var years = { };
var months = { 1: 'Gen', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' };
for( var i = 1; i < 32; i += 1 ) {
days[i] = i;
}
for( i = now.getFullYear()-100; i < now.getFullYear()+1; i += 1 ) {
years[i] = i;
}
SpinningWheel.addSlot(years, 'right', 1999);
SpinningWheel.addSlot(months, '', 4);
SpinningWheel.addSlot(days, 'right', 12);
SpinningWheel.setCancelAction(cancel);
SpinningWheel.setDoneAction(done);
SpinningWheel.open();
}
function openOneSlot() {
SpinningWheel.addSlot({1: 'Ichi', 2: 'Ni', 3: 'San', 4: 'Shi', 5: 'Go'});
SpinningWheel.setCancelAction(cancel);
SpinningWheel.setDoneAction(done);
SpinningWheel.open();
}
function done() {
var results = SpinningWheel.getSelectedValues();
document.getElementById('result').innerHTML = 'values: ' + results.values.join(' ') + '<br />keys: ' + results.keys.join(', ');
}
function cancel() {
document.getElementById('result').innerHTML = 'cancelled!';
}
window.addEventListener('load', function(){ setTimeout(function(){ window.scrollTo(0,0); }, 100); }, true);
</script>
<style type="text/css">
body { text-align:center; font-family:helvetica; }
button { font-size:16px; }
#result { margin:10px; background:#aaa; -webkit-border-radius:8px; padding:8px; font-size:18px; }
</style>
</head>
<body>
<p>Spinning Wheel slot machine alike widget for iPhone and iPod touch.
This demo works only on the simulator and the real devices, it does not work on any other browser.</p>
<button onclick="openBirthDate()">birth date</button>
<button onclick="openWeight()">weight</button>
<button onclick="openOneSlot()">1 slot</button>
<p id="result">results</p>
<p><a href="http://cubiq.org/spinning-wheel-on-webkit-for-iphone-ipod-touch/11">Read the related article on cubiq.org</a></p>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
this text is here for the sole purpose of making the page longer
</body>
</html>
And this is the CSS:
#sw-wrapper {
position:absolute; z-index:1000;
left:0;
width:100%;
font-family:helvetica, sans-serif;
background:rgba(0,0,0,0.7);
text-align:left;
}
#sw-header {
position:relative;
width:100%; height:43px;
border-top:1px solid #000; border-bottom:1px solid #000;
background:url(sw-header.png) 0 0 repeat-x;
opacity: 0.9;
}
#sw-cancel, #sw-done {
position:absolute;
top:7px;
height:20px; line-height:20px;
padding:0 5px; margin:0;
border-width:5px;
font-size:12px; font-weight:bold;
text-shadow:rgba(0,0,0,0.8) 0 -1px 0;
color:#fff;
}
#sw-cancel {
left:7px;
float:left;
-webkit-border-image:url(sw-button-cancel.png) 5;
}
#sw-done {
right:7px;
float:right;
-webkit-border-image:url(sw-button-done.png) 5;
}
.sw-pressed { opacity:0.4; }
#sw-slots-wrapper {
position:relative; z-index:999;
display:block;
height:215px;
padding:0 11px;
overflow:hidden;
}
#sw-slots {
display:table;
width:100%;
background:#fcfcfc;
}
#sw-slots div {
display:table-cell;
height:100%;
padding-top:86px;
border-left:2px solid #0d0e0f;
background-color:#fcfcfc;
background-image: url(sw-slot-border.png);
background-position: 0 0, 100% 0;
background-repeat: repeat-y;
}
#sw-slots div:first-child { border:0; }
#sw-slots ul {
padding:0 0 85px 0; margin:0;
list-style:none;
}
#sw-slots .sw-right { text-align:right; }
#sw-slots .sw-shrink { width:1%; }
#sw-slots .sw-readonly { background:#ddd; }
#sw-slots li {
padding:0 8px;
height:44px;
overflow:hidden;
font:bold 24px/44px Helvetica,sans-serif;
}
#sw-frame {
position:absolute; z-index:1000;
left:0; right:0; bottom:0;
height:183px;
border-width:16px;
-webkit-border-image:url(sw-alpha.png) 16;
}