A Pen by Luiz “Felds” Liscia on CodePen.
Last active
September 27, 2016 16:12
-
-
Save felds/514140add896803f4c1ac6f35a249cb3 to your computer and use it in GitHub Desktop.
JQuery Scrubber
This file contains hidden or 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
| <div class="slideshow" id="slideshow"> | |
| <div class="slideshow__bgs"> | |
| <div style="background-image: url(https://unsplash.it/800/600?image=4)"></div> | |
| <div style="background-image: url(https://unsplash.it/800/600?image=3)"></div> | |
| <div style="background-image: url(https://unsplash.it/800/600?image=2)"></div> | |
| <div style="background-image: url(https://unsplash.it/800/600?image=1)"></div> | |
| </div> | |
| <div class="slideshow__fg"> | |
| <div class="scrubber" data-scrubber-steps="4" id="scr-slideshow"></div> | |
| </div> | |
| </div> | |
| <h1>Examples</h1> | |
| <h3>2 steps with custom elements</h3> | |
| <div class="scrubber" id="scrubber-1" data-scrubber-steps="2"> | |
| <div class="scrubber__track"> | |
| <b class="scrubber__handle bleu"></b> | |
| </div> | |
| </div> | |
| <h3>3 steps</h3> | |
| <div class="scrubber" id="scrubber-2" data-scrubber-steps="3"></div> | |
| <h3>1 step (fluid)<h3> | |
| <div class="scrubber" id="scrubber-3" data-scrubber-steps="1"></div> | |
| <h3>4 steps</h3> | |
| <div class="scrubber" id="scrubber-4" data-scrubber-steps="4"></div> | |
| <h3>5 steps</h3> | |
| <div class="scrubber" id="scrubber-5" data-scrubber-steps="5"></div> | |
| <h3>C/ Saveiro</h3> | |
| <div class="scrubber" id="scr-saveiro" data-scrubber-steps="4"></div> | |
| <img src="http://novasaveiro.vw.com.br/img/large/05_360_02_Saveiro_Trend_Volkswagen_00001.png" alt="Saveiro" id="img-saveiro" /> | |
| <h1>TODOs</h1> | |
| <ul> | |
| <li>Add click to scroll</li> | |
| <li>✓ Get steps from data</li> | |
| <li>✓ WTF IS X?!</li> | |
| <li>✓ use percents</li> | |
| <li>✓ enforce boudaries</li> | |
| <li>✓ move events to the scrubber (instead of triggering from the handle)</li> | |
| </ul> |
This file contains hidden or 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
| console.clear() | |
| // Loading bar for console output | |
| const loadingBar = (relative, width = 50) => { | |
| const loaded = Math.floor(width * relative) | |
| return '█'.repeat(loaded) + '▒'.repeat(width - loaded) | |
| } | |
| // mix colors for testing relative stuff | |
| const mid = (a, b, amp) => | |
| a + (b - a) * amp | |
| const mixHex = (a, b, amp) => | |
| ('00' + Math.floor(mid(parseInt(a, 16), parseInt(b, 16), amp)).toString(16)).substr(-2) | |
| const cleanupHexColor = (color) => color.match(/[0-9a-f]{2}/ig) | |
| const gradient = (from, to) => (amp) => { | |
| const fromVals = cleanupHexColor(from) | |
| const toVals = cleanupHexColor(to) | |
| return '#' + | |
| mixHex(fromVals[0], toVals[0], amp) + | |
| mixHex(fromVals[1], toVals[1], amp) + | |
| mixHex(fromVals[2], toVals[2], amp) | |
| } | |
| const grad1 = gradient('#ffd700', '#ed143d') | |
| expect(grad1(0)).toBe('#ffd700') | |
| expect(grad1(.5)).toBe('#f6751e') | |
| expect(grad1(.1234)).toBe('#fcbe07') | |
| expect(grad1(1)).toBe('#ed143d') | |
| const gr1 = gradient('#ffffff', '#ffff00') | |
| console.log(gr1(.90)) | |
| // initialize stuff | |
| jQuery(($) => { | |
| let x = $('.scrubber') | |
| .scrubber({ steps: 0 }) | |
| ; | |
| $('#scrubber-3').on('change', (e, v) => console.log(loadingBar(v.relative))) | |
| const grad = gradient('#397ee3', '#dc143c') | |
| const scr2 = $('#scrubber-2') | |
| scr2.on('change', (e, v) => scr2.find('.scrubber__handle').css({ backgroundColor: grad(v.relative) })) | |
| const grad2 = gradient('#F57D7D', '#7DD1F5') | |
| const scr3 = $('#scrubber-3') | |
| scr3 | |
| .on('change', (e, v) => { | |
| scr3.css({ backgroundColor: grad2(v.relative) }) | |
| }) | |
| .trigger('update') | |
| const scr5 = $('#scrubber-5') | |
| scr5.on('change', (e, v) => { | |
| let steps = [ 'crimson', '#808000', '#00551C', 'blue', 'purple' ] | |
| scr5.css({ backgroundColor: steps[v.step] }) | |
| }).trigger('update') | |
| // saveiro | |
| const scrSaveiro = $('#scr-saveiro') | |
| const imgSaveiro = $('#img-saveiro') | |
| const getSavImage = (n = 1) => { | |
| let id = ('0000000' + n).substr(-5) | |
| return `http://novasaveiro.vw.com.br/img/large/05_360_02_Saveiro_Trend_Volkswagen_${id}.png` | |
| } | |
| scrSaveiro.on('change', (e, v) => { | |
| imgSaveiro.prop({ src: getSavImage(Math.floor(v.relative * 59) + 1) }) | |
| }).trigger('update') | |
| for (let i = 1; i <= 60; i++) { | |
| let img = new Image() | |
| img.src = getSavImage(i) | |
| } | |
| scr5.on('change', (e, v) => { | |
| imgSaveiro.css({ filter: `hue-rotate(${Math.floor(v.relative*288)}deg)` }) | |
| }).trigger('update') | |
| const slideshow = $('#slideshow') | |
| const slides = slideshow.find('.slideshow__bgs div') | |
| slideshow.find('#scr-slideshow').on('change', (e, v) => { | |
| // let slice = v.relative * slides.length | |
| let rel = v.relative | |
| let len = slides.length | |
| console.clear() | |
| slides.each(i => { | |
| let opacity = 1 + ((rel * len) - (len - i) + (i / (len - 1))) | |
| console.log({opacity}) | |
| slides.eq(i).css({ opacity }) | |
| }) | |
| let factor = 4 | |
| let t4 = v.relative * 4 | |
| //console.log( | |
| // slides.length, | |
| // t4.toFixed(3), | |
| // //v.relative.toFixed(3), | |
| // //(1-v.relative).toFixed(3), | |
| // //(2-v.relative).toFixed(3), | |
| // (t4-3 + (0/(factor-1))).toFixed(3), | |
| // (t4-2 + (1/(factor-1))).toFixed(3), | |
| // (t4-1 + (2/(factor-1))).toFixed(3), | |
| // (t4-0 + (3/(factor-1))).toFixed(3), | |
| //) | |
| // const sliceSize = 1 / 3 // (slides.length - 1) | |
| // console.log(1 - sliceSize) | |
| // slides.eq(1).css({ | |
| // opacity: (sliceSize * 1) - v.relative, | |
| // }) | |
| }).trigger('update') | |
| }) | |
| // plugin | |
| $.fn.scrubber = function (receivedOptions = {}) { | |
| const defaultOptions = { | |
| steps: 1, | |
| animationSpeed: 'fast', | |
| animationEasing: 'swing', | |
| trackClass: 'scrubber__track', | |
| handleClass: 'scrubber__handle', | |
| } | |
| const options = $.extend(defaultOptions, receivedOptions) | |
| const $root = $(':root') | |
| this.each(i => { | |
| const $scrubber = this.eq(i) | |
| let steps = $scrubber.data('scrubber-steps') || options.steps | |
| let offsetFromHandle = 0 | |
| // support elements | |
| const $track = $scrubber.find('.' + options.trackClass).length | |
| ? $scrubber.find('.' + options.trackClass).eq(0) | |
| : ($(`<div class="${options.trackClass}" />`).appendTo($scrubber)) | |
| const $handle = $track.find('.' + options.handleClass).length | |
| ? $track.find('.' + options.handleClass).eq(0) | |
| : ($(`<b class="${options.handleClass}" />`).appendTo($track)) | |
| $handle.css({ | |
| cursor: 'pointer', | |
| position: 'absolute', | |
| left: 0, | |
| top: 0, | |
| }) | |
| $track.css({ | |
| position: 'relative', | |
| marginRight: $handle.outerWidth(), | |
| }) | |
| $handle.on('mousedown', e => { | |
| offsetFromHandle = e.offsetX | |
| startDragging() | |
| e.preventDefault() | |
| }) | |
| const trackMovement = (e) => { | |
| // track the movement of the mouse on :root | |
| const offset = e.screenX | |
| const offsetFromParent = $track.offset().left | |
| const parentWidth = $track.width() | |
| let absolute = offset - offsetFromParent - offsetFromHandle | |
| if (absolute < 0) | |
| absolute = 0 | |
| if (absolute > parentWidth) | |
| absolute = parentWidth | |
| let relative = absolute / parentWidth | |
| $handle.css({ left: `${relative * 100}%` }) | |
| updateValue() | |
| } | |
| const startDragging = () => { | |
| $scrubber.trigger('startdragging') | |
| // attach trackMovement to root | |
| $root | |
| .on('mousemove', trackMovement) | |
| .on('mouseup', stopDragging) | |
| } | |
| const stopDragging = () => { | |
| const stop = getPosition().stop | |
| // animate to the center of the step | |
| $handle.animate( | |
| { left: `${stop * 100}%` }, { | |
| easing: options.animationEasing, | |
| duration: options.animationSpeed, | |
| progress: updateValue, | |
| } | |
| ) | |
| // trigger event | |
| // @TODO add position to the event | |
| $scrubber.trigger('stopdragging') | |
| // detach trackMOvement from root | |
| $root | |
| .off('mousemove', trackMovement) | |
| .off('mouseup', stopDragging) | |
| } | |
| const updateValue = () => { | |
| // get the current values and trigger a 'change' event | |
| $scrubber.trigger('change', [ getPosition() ]) | |
| } | |
| const getPosition = () => { | |
| const absolute = $handle.position().left | |
| const relative = absolute / $track.width() | |
| const sliceSize = 1 / (steps * 2 - 2) | |
| const step = Math.floor((relative + sliceSize) / (sliceSize * 2)) | |
| const stop = sliceSize * step * 2 | |
| return { absolute, relative, stop, step, sliceSize } | |
| } | |
| $scrubber.trigger('init', [ getPosition() ]) | |
| $scrubber.on('update', updateValue) | |
| }) | |
| return this | |
| } |
This file contains hidden or 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
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/expect/1.20.2/expect.min.js"></script> |
This file contains hidden or 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
| $color: mix(#333, dodgerblue); | |
| :root { | |
| background-color: whitesmoke; | |
| box-sizing: border-box; | |
| * { box-sizing: inherit } | |
| } | |
| body { | |
| margin: 2rem; | |
| } | |
| .scrubber { | |
| height: 2rem; | |
| outline: 1px solid $color; | |
| margin: 2rem; | |
| background: mix($color, white) | |
| } | |
| .scrubber__track { | |
| height: 100%; | |
| } | |
| .scrubber__handle { | |
| height: 100%; | |
| background-color: $color; | |
| width: 2rem; | |
| } | |
| #scrubber-2 { width: 80% } | |
| #scrubber-1 .scrubber__handle { width: 4rem; border-radius: 1rem; } | |
| .bleu { | |
| background-color: dodgerblue; | |
| } | |
| #img-saveiro { | |
| width: 100%; | |
| height: auto; | |
| } | |
| .slideshow { | |
| width: 800px; | |
| height: 600px; | |
| position: relative; | |
| margin: 2rem auto; | |
| } | |
| .slideshow__bgs { | |
| position: absolute; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| outline: 1px solid crimson; | |
| > div { | |
| position: absolute; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 0; | |
| } | |
| } | |
| .slideshow__fg { | |
| position: absolute; | |
| bottom: 2rem; | |
| width: 100%; | |
| z-index: 9; | |
| padding: 0 4rem; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment