Created
March 12, 2015 04:44
-
-
Save JoshBarr/a33232af242bc5a2f5c3 to your computer and use it in GitHub Desktop.
Range slider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Slider test</title> | |
<style type="text/css"> | |
body { | |
font-family: pitch, courier; | |
margin: 4em; | |
} | |
.slider { | |
height: 100px; | |
background: #444; | |
margin-left: 1em; | |
margin-right: 1em; | |
} | |
.handle { | |
height: 100%; | |
cursor: pointer; | |
width: 0; | |
position: relative; | |
} | |
.actual-handle { | |
width: 2em; | |
height: 100%; | |
margin-left: -1em; | |
background: #999; | |
} | |
.inactive { | |
display: none; | |
} | |
.label { | |
position: absolute; | |
bottom: 115%; | |
width: 3em; | |
background: #eee; | |
margin-left: -1.5em; | |
text-align: center; | |
padding: .25em .5em; | |
} | |
.label:after { | |
border: solid .5em transparent; | |
border-top-color: #eee; | |
content: ""; | |
position: absolute; | |
top: 100%; | |
left: 50%; | |
margin-left: -.5em; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="slider inactive"> | |
<div class="handle"> | |
<div class="actual-handle"> | |
</div> | |
<div class="label inactive"> | |
200px | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
module = {}; | |
</script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script> | |
<script type="text/javascript" src="slider.js"></script> | |
<script type="text/javascript"> | |
function init() { | |
var el = document.querySelector('.slider'); | |
var label = document.querySelector('.label'); | |
var minPointSize = 20; | |
var maxPointSize = 300; | |
var range = { | |
min: minPointSize, | |
max: maxPointSize | |
}; | |
function getPointSize(val) { | |
var r = range.max - range.min; | |
var n = val * r; | |
return Math.floor(range.min + n); | |
} | |
var slider = new UISliderComponent({ | |
el: el, | |
handle: document.querySelector('.handle') | |
}); | |
slider.setValue(.5, true); | |
el.classList.remove("inactive"); | |
slider.onStart = function(e) { | |
console.log("start", e); | |
console.log(getPointSize(e)); | |
label.textContent = getPointSize(e) + "px"; | |
label.classList.remove('inactive'); | |
} | |
slider.onEnd = function(e) { | |
console.log("end", e); | |
console.log(getPointSize(e)); | |
label.classList.add('inactive'); | |
} | |
slider.onMove = function(e) { | |
console.log("move", e); | |
console.log(getPointSize(e)); | |
label.textContent = getPointSize(e) + "px"; | |
} | |
} | |
document.addEventListener("DOMContentLoaded", function(e) { | |
init(); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Point(x,y) { | |
this.x = x; | |
this.y = y; | |
} | |
Point.prototype.get = function get() { | |
return { | |
x: this.x, | |
y: this.y | |
} | |
} | |
Point.prototype.set = function set(x, y) { | |
this.x = x; | |
this.y = y; | |
return this.get(); | |
} | |
/** | |
* A simple range slider, sans jquery. | |
* | |
* @param {Object} options A hash of properties to be applied to the object | |
*/ | |
function UISliderComponent(options) { | |
// Set instance defaults in constructor to avoid accidental | |
// static variables :) | |
var opt, defaults = { | |
el: null, | |
handle: null, | |
track: null, | |
responsive: true, | |
value: 0, | |
useRequestAnimationFrame: true, | |
min: 0, | |
max: 1, | |
steps: null, | |
boundEvents: ["mousedown","touchstart", "selectstart"] | |
}; | |
for (opt in defaults) { | |
this[opt] = defaults[opt]; | |
} | |
for (opt in options) { | |
this[opt] = options[opt]; | |
} | |
if (!this.el || !this.handle) { | |
console.log("Check you've got a handle and el set"); | |
return; | |
} | |
this.throttledLayoutUpdate = _.debounce( this.calculateLayout, 500 ); | |
this.isTouch = window.DocumentTouch && document instanceof DocumentTouch; | |
if (this.responsive) { | |
window.addEventListener("resize", this, false); | |
} | |
this.document = document.documentElement; | |
// Bind returns a new function ref | |
this.handleDown = this.handleDown.bind(this); | |
this.handleEnd = this.handleEnd.bind(this); | |
this.handleMove = this.handleMove.bind(this); | |
this.downEvent = "mousedown"; | |
this.moveEvent = "mousemove"; | |
this.endEvent = "mouseup"; | |
if (this.isTouch) { | |
this.downEvent = "touchstart"; | |
this.moveEvent = "touchmove"; | |
this.endEvent = "touchend"; | |
} | |
// Handle IE's select start | |
this.el.addEventListener("selectstart", this, false); | |
this.el.addEventListener("mousedown", this, false); | |
this.el.addEventListener("touchstart", this, false); | |
this.initialize(); | |
}; | |
UISliderComponent.prototype = { | |
initialize: function() { | |
this.position = 0; | |
this.pointer = new Point(0, 0); | |
this.calculateLayout(); | |
}, | |
handleEvent: function(e) { | |
switch(e.type) { | |
case "resize": | |
// Only catch horizontal events | |
var iw = window.innerWidth; | |
if (!this.innerWidth && this.innerWidth !== iw) { | |
this.throttledLayoutUpdate(); | |
this.innerWidth = iw; | |
} | |
break; | |
case "touchstart": | |
this.handleDown(e); | |
break; | |
case "touchcancel": | |
this.handleEnd(e); | |
break; | |
case "mousedown": | |
this.handleDown(e); | |
break; | |
case "selectstart": | |
e.preventDefault(); | |
break; | |
} | |
}, | |
trigger: function(methodName) { | |
if (this[methodName] && typeof this[methodName] === "function") { | |
this[methodName](this.value, this.position); | |
} | |
}, | |
handleDown: function(e) { | |
// e.preventDefault(); | |
this.document.addEventListener("mousemove", this.handleMove, false); | |
this.document.addEventListener("mouseup", this.handleEnd, false); | |
if (e.touches) { | |
this.document.addEventListener("touchmove", this.handleMove, false); | |
this.document.addEventListener("touchend", this.handleEnd, false); | |
} | |
// Especially for responsive, get the layout/bounds when the drag | |
// is started | |
this.calculateLayout(); | |
var pointer = this.setPointerCoords(e); | |
this.isDirty = true; | |
this.trigger("onStart"); | |
// Don't set the position when the handle is clicked. | |
// It should already be set by calculateLayout. | |
if (e.target === this.handle) { | |
this.trigger("onHandle"); | |
return; | |
} | |
this.setPosition(pointer.x); | |
}, | |
handleMove: function(e) { | |
e.preventDefault(); | |
var pointer = this.setPointerCoords(e); | |
this.setPosition(pointer.x); | |
this.trigger("onMove"); | |
}, | |
handleEnd: function(e) { | |
// e.preventDefault(); | |
this.document.removeEventListener("mousemove", this.handleMove, false); | |
this.document.removeEventListener("mouseup", this.handleEnd, false); | |
this.document.removeEventListener("touchmove", this.handleMove, false); | |
this.document.removeEventListener("touchend", this.handleEnd, false); | |
this.trigger("onEnd"); | |
}, | |
setPosition: function(pos){ | |
var value, left; | |
var localX = pos - this.elementLeft; | |
var percentage = (localX / this.trackWidth); | |
if (percentage < 0) { percentage = 0; } | |
if (percentage > 1) { percentage = 1; } | |
this.setValue(percentage); | |
this.position = percentage; | |
if (this.onSlide && typeof this.onSlide === 'function') { | |
this.onSlide(left, value); | |
} | |
}, | |
setPointerCoords: function(e) { | |
var x, y; | |
if (e.touches) { | |
x = e.touches[0].clientX; | |
y = e.touches[0].clientY; | |
} else if (e.currentPoint) { | |
x = e.currentPoint.x; | |
y = e.currentPoint.y; | |
} else { | |
x = (e.pageX || e.clientX); | |
y = (e.pageY || e.clientY); | |
} | |
return this.pointer.set(x, y); | |
}, | |
setValue: function(num, triggerSet) { | |
var value; | |
if (this.steps) { | |
value = this.getStepValue(num); | |
} else { | |
value = this.getValFromFloat(num); | |
} | |
this.updateUI(value); | |
if (value !== this.value || triggerSet) { | |
this.value = value; | |
if (this.isDirty) { | |
this.trigger("onChange"); | |
} | |
} | |
return value; | |
}, | |
setStep: function(stepNumber) { | |
var stepValue; | |
if (!this.step) { | |
return; | |
} | |
if (stepNumber > this.steps) { | |
stepNumber = this.steps; | |
} | |
stepValue = this.step * stepNumber; | |
this.isDirty = true; | |
this.setValue(stepValue); | |
}, | |
updateUI: function(num) { | |
var cssPercentage = (num * 100); | |
// Update UI | |
if (this.fill) { | |
this.fill.style.width = cssPercentage + '%'; | |
} | |
this.handle.style.left = cssPercentage + '%'; | |
}, | |
getValueAsFloat: function(value) { | |
var range = this.max - this.min; | |
var valRatio = value / range; | |
return valRatio; | |
}, | |
getValFromFloat: function(num) { | |
var range = this.max - this.min; | |
return (num * range) - this.min; | |
}, | |
getStepValue: function(percentage) { | |
var rel = (((percentage) * (this.max - this.min)) + this.min); | |
var value = this.step * Math.ceil(rel / this.step); | |
return Number((value).toFixed(2)); | |
}, | |
calculateLayout: function() { | |
var bounds = this.el.getBoundingClientRect(); | |
this.elementLeft = Math.ceil(bounds.left); | |
this.elementRight = Math.floor(bounds.right); | |
this.handleWidth = this.handle.offsetWidth; | |
this.trackWidth = this.el.offsetWidth; | |
this.maxHandleX = this.trackWidth - this.handleWidth; | |
this.handleCenter = this.handleWidth / 2; | |
if (this.steps) { | |
this.step = 1 / this.steps; | |
} | |
this.position = 0; | |
this.setValue(this.value, true); | |
}, | |
// Removes all event listeners we've stored references to. | |
remove: function() { | |
var events = this.boundEvents; | |
for (var e in events) { | |
this.el.removeEventListener(e, this, false); | |
} | |
window.removeEventListener("resize", this, false); | |
this.document.removeEventListener("mousemove", this.handleMove, false); | |
this.document.removeEventListener("mouseup", this.handleEnd, false); | |
this.document.removeEventListener("touchmove", this.handleMove, false); | |
this.document.removeEventListener("touchend", this.handleEnd, false); | |
return true; | |
} | |
}; | |
module.exports = UISliderComponent; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment