├── smiley-slider.png ├── README ├── LICENSE ├── index.html └── smiley-slider.js /smiley-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dglittle/smiley-slider/HEAD/smiley-slider.png -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is designed to let users rate how happy they are with something using a 2 | smiley faced slider. 3 | 4 | Use: 5 | 6 | // add the slider to a div with id "slider" 7 | var s = new SmileySlider(document.getElementById("slider")) 8 | 9 | s.position(0) // make it sad 10 | s.position(1) // make it happy 11 | 12 | var p = s.position() // get it's position 13 | s.position(p / 2) // make it half as happy 14 | 15 | s.position(function (p) { 16 | // do something when it changes 17 | }) 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 oDesk Corporation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | smiley-slider 6 | 8 | 9 | 10 | 11 | 12 | 35 | 36 | 37 | 38 | 39 |
source code
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /smiley-slider.js: -------------------------------------------------------------------------------- 1 | 2 | function SmileySlider(container, imgSrc) { 3 | if (!imgSrc) 4 | imgSrc = "smiley-slider.png" 5 | 6 | var width = 329 7 | var height = 37 8 | 9 | var headWidth = 40 10 | var maxHeadX = width - headWidth + 1 11 | 12 | var base = document.createElement('div') 13 | base.style.width = width + "px" 14 | base.style.height = height + "px" 15 | base.style.background = "white" 16 | 17 | var track = document.createElement('div') 18 | track.style.width = width + "px" 19 | track.style.height = 6 + "px" 20 | track.style.marginRight = '-' + track.style.width 21 | track.style.marginBottom = '-' + track.style.height 22 | track.style.position = "relative" 23 | track.style.top = 15 + "px" 24 | track.style.background = "url('" + imgSrc + "')" 25 | base.appendChild(track) 26 | 27 | var head = document.createElement('div') 28 | head.style.width = headWidth + "px" 29 | head.style.height = height + "px" 30 | head.style.marginRight = '-' + head.style.width 31 | head.style.marginBottom = '-' + head.style.height 32 | head.style.position = "relative" 33 | head.style.background = "url('" + imgSrc + "') scroll 0px -6px" 34 | base.appendChild(head) 35 | 36 | var face = document.createElement('canvas') 37 | face.style.width = 36 + "px" 38 | face.style.height = 37 + "px" 39 | face.style.position = "relative" 40 | face.style.left = 4 + "px" 41 | face.width = "36" 42 | face.height = "37" 43 | head.appendChild(face) 44 | 45 | var glass = document.createElement('div') 46 | glass.style.width = width + "px" 47 | glass.style.height = height + "px" 48 | glass.style.marginRight = '-' + glass.style.width 49 | glass.style.marginBottom = '-' + glass.style.height 50 | glass.style.position = "relative" 51 | base.appendChild(glass) 52 | 53 | container.appendChild(base) 54 | 55 | ////////////////////////////////////////////////////////////// 56 | // head position 57 | 58 | var onHeadMove = null 59 | 60 | function positionInt(e) { 61 | if (e === undefined) { 62 | return getPos(head).x - getPos(base).x 63 | } else { 64 | head.style.left = Math.round(cap(e, 0, maxHeadX)) + "px" 65 | var p = position() 66 | drawFace(face, 100, p, 0.8) 67 | if (onHeadMove) onHeadMove(p) 68 | } 69 | } 70 | 71 | function position(e) { 72 | if (e === undefined) { 73 | return lerp(0, 0, maxHeadX, 1, positionInt()) 74 | } else if (typeof(e) == "function") { 75 | onHeadMove = e 76 | } else { 77 | positionInt(lerp(0, 0, 1, maxHeadX, e)) 78 | } 79 | } 80 | 81 | this.position = position 82 | setTimeout(function () { 83 | position(0.5) 84 | }, 0) 85 | 86 | ////////////////////////////////////////////////////////////// 87 | // mouse 88 | 89 | glass.onmousedown = function (e) { 90 | e.preventDefault() 91 | var pos = getRelPos(glass, e) 92 | 93 | var grabX = headWidth / 2 94 | var headX = positionInt() 95 | if (pos.x >= headX && pos.x < headX + headWidth) { 96 | grabX = pos.x - headX 97 | } 98 | 99 | positionInt(pos.x - grabX) 100 | 101 | var oldMove = document.onmousemove 102 | document.onmousemove = function (e) { 103 | var pos = getRelPos(glass, e) 104 | 105 | positionInt(pos.x - grabX) 106 | } 107 | 108 | var oldUp = document.onmouseup 109 | document.onmouseup = function (e) { 110 | document.onmousemove = oldMove 111 | document.onmouseup = oldUp 112 | } 113 | } 114 | 115 | ////////////////////////////////////////////////////////////// 116 | // touch 117 | 118 | glass.ontouchstart = function (e) { 119 | e.preventDefault() 120 | var pos = getRelPos(glass, e.touches[0]) 121 | 122 | var grabX = headWidth / 2 123 | var headX = positionInt() 124 | if (pos.x >= headX && pos.x < headX + headWidth) { 125 | grabX = pos.x - headX 126 | } 127 | 128 | positionInt(pos.x - grabX) 129 | 130 | var oldMove = document.ontouchmove 131 | document.ontouchmove = function (e) { 132 | e.preventDefault(); 133 | var pos = getRelPos(glass, e.touches[0]) 134 | positionInt(pos.x - grabX) 135 | } 136 | 137 | var oldEnd = document.ontouchend; 138 | var oldCancel = document.ontouchcancel 139 | document.ontouchend = document.ontouchcancel = function (e) { 140 | document.ontouchmove = oldMove 141 | document.ontouchend = oldEnd 142 | document.ontouchcancel = oldCancel; 143 | } 144 | } 145 | 146 | ////////////////////////////////////////////////////////////// 147 | // core drawing code 148 | 149 | var PI180 = Math.PI / 180; 150 | 151 | function drawFace(canvas, radius, emotion, innerScale) { 152 | emotion = Math.max(0, Math.min(1, emotion)); 153 | var diam = radius * 2; 154 | 155 | var ctx = canvas.getContext('2d'); 156 | ctx.clearRect (0, 0, diam, diam); 157 | 158 | ctx.beginPath(); 159 | ctx.fillStyle = '#414084'; 160 | drawSmile(ctx, 15.5, 20, innerScale, emotion); 161 | ctx.closePath(); 162 | ctx.fill(); 163 | 164 | ctx.beginPath(); 165 | ctx.lineWidth = 1; 166 | ctx.strokeStyle = '#414084'; 167 | drawEyeBrows(ctx, 9.5, 16, 23, 16, 7, 5, emotion); 168 | ctx.stroke(); 169 | }; 170 | 171 | function drawSmile(context, mainRadius, offsetY, innerScale, emotion) { 172 | var eased = 1 - easeInQuad(emotion, 0, 1, 1); 173 | var innerScale = innerScale - (eased * 0.4); 174 | var curveOffset = easeInCubic(emotion, 0.1, 0.6, 1); 175 | drawArc(context, mainRadius, offsetY, innerScale, emotion); 176 | drawArc(context, mainRadius, offsetY, innerScale, emotion, curveOffset, true); 177 | } 178 | 179 | function easeInCubic(t, b, c, d) { 180 | return c*(t/=d)*t*t + b; 181 | } 182 | 183 | function easeInQuad(t, b, c, d) { 184 | return c*(t/=d)*t + b; 185 | } 186 | 187 | function drawArc(context, mainRadius, offsetY, innerScale, emotion, curveOffset, reverseX) { 188 | curveOffset = (curveOffset === undefined) ? 0 : curveOffset; 189 | 190 | var innerRadius = mainRadius * innerScale; 191 | var pad = mainRadius - innerRadius; 192 | var diam = innerRadius * 2; 193 | 194 | var SEGS = 16; 195 | 196 | var theta = 360 / SEGS; 197 | var emoScale = (emotion - 0.5) * 2; 198 | 199 | var sides = [pad, pad + diam]; 200 | var ct = [[0, 0], [0, 0]]; 201 | 202 | ct[0][0] = innerRadius * Math.cos((theta * 3) * PI180) + pad ; 203 | ct[0][1] = innerRadius * Math.sin((theta * 3) * PI180) * emoScale + offsetY + pad - (curveOffset * mainRadius); 204 | 205 | ct[1][0] = innerRadius * Math.cos((theta * 5) * PI180) + pad + (innerRadius * 2); 206 | ct[1][1] = innerRadius * Math.sin((theta * 5) * PI180) * emoScale + offsetY + pad - (curveOffset * mainRadius); 207 | 208 | if (reverseX) { 209 | sides.reverse(); 210 | ct.reverse(); 211 | } 212 | 213 | context.moveTo(sides[0], offsetY + pad); 214 | context.bezierCurveTo(ct[0][0], ct[0][1], ct[1][0], ct[1][1], sides[1], offsetY + pad); 215 | } 216 | 217 | function drawEyeBrows(context, x1, y1, x2, y2, width, distance, emotion) { 218 | var a = (emotion - 0.5) * 30; 219 | var hW = width * 0.5; 220 | 221 | var l1 = rotZ(-hW, -distance, -a); 222 | var l2 = rotZ(hW , -distance, -a); 223 | 224 | var r1 = rotZ(-hW, -distance, a); 225 | var r2 = rotZ(hW , -distance, a); 226 | 227 | context.moveTo(l1[0] + x1, l1[1] + y1); 228 | context.lineTo(l2[0] + x1, l2[1] + y1); 229 | 230 | context.moveTo(r1[0] + x2, r1[1] + y2); 231 | context.lineTo(r2[0] + x2, r2[1] + y2); 232 | } 233 | 234 | function rotZ(x, y, angle) { 235 | var cos = Math.cos(angle * PI180); 236 | var sin = Math.sin(angle * PI180); 237 | var tx = x * cos - y * sin; 238 | var ty = x * sin + y * cos; 239 | return [tx, ty]; 240 | } 241 | 242 | ////////////////////////////////////////////////////////////// 243 | // utils 244 | 245 | function cap(t, mi, ma) { 246 | if (t < mi) return mi 247 | if (t > ma) return ma 248 | return t 249 | } 250 | 251 | function lerp(t0, v0, t1, v1, t) { 252 | return (t - t0) * (v1 - v0) / (t1 - t0) + v0 253 | } 254 | 255 | function getPos(e) { 256 | var x = 0, y = 0 257 | while (e != null) { 258 | x += e.offsetLeft 259 | y += e.offsetTop 260 | e = e.offsetParent 261 | } 262 | return {x : x, y : y} 263 | } 264 | 265 | function getRelPos(to, positionedObject) { 266 | var pos = getPos(to) 267 | return { 268 | x : positionedObject.pageX - pos.x, 269 | y : positionedObject.pageY - pos.y 270 | } 271 | } 272 | } 273 | 274 | --------------------------------------------------------------------------------