简体   繁体   中英

How to make a HTML5 spinning list/rotating wheel selector/picker

My question is about an answer given by user ByteHamster here: How to create JavaScript/HTML5 Spinner List with images? in the answer he/she gives an example of how to create a scrolling animation with html, css and Javascript. The animation allows the user to scroll through the numbers by clicking above or below the selected number on screen, and the selected number is shown in a div below the animation.

I was wondering if it's possible to do something similar to this but instead of having an image moving up and down, can it be turned into a number wheel? By that I mean, in the example above, the scrolling stops in one direction once the number reaches 0, I'm wondering if it's possible to create a wheel, where a user could constantly spin it from top to bottom, or bottom to top if they wished to do so. Would this require using 3d interactive animation software?

I've seen this question: HTML5/CSS3 - how to make "endless" rotating background - 360 degrees panorama but I'm unsure if the answers are applicable to my project as they don't seem to be interactive.

As user ByteHamster's answer is over 3 years old, I was wondering if there's a better way to achieve this effect with a html5 animation? And am I correct in thinking that the Javascript in the example would make it not work on some devices/browsers that don't have Javascript enabled? Would a html5 approach be the best way to ensure the effect works on most devices/browsers?

Here's what I put together from the info provided... works with the mousewheel, swiping and clicking on the top and bottom numbers. Infinite as requested of course. No special perspective style (yet) but I thought it looked quite decent as is. Could still be an option naturally. Didn't use the plugin I linked to in the comments or requestAnimationFrame but jQuery animate() is quite a good tool for this. The library has great cross browser support (that's it's strength actually), all it needs is a link to it for the JavaScript to be able to get executed. You could use a CDN, this version also works for IE8- :

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

For the best cross browser support on using the mousewheel, this plugin was included :

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.js"></script>

https://plugins.jquery.com/mousewheel/

Just a basic parent and styling with spans for each number, a few prepended in case of going up.

 $(function() { var gate = $(window), cog = $('#rotator'), digit = cog.find('span'), field = $('#result'), slot = digit.height(), base = 1.5*slot, up, swipe; if (document.readyState == 'complete') interAction(); else gate.one('load', interAction); function interAction() { field.text(0); cog.scrollTop(base).fadeTo(0,1).mousewheel(function(turn, delta) { if (isBusy()) return false; up = delta > 0; nextNumber(); return false; }); digit.on('touchstart', function(e) { var begin = e.originalEvent.touches[0].pageY; digit.on('touchmove', function(e) { var yaw = e.originalEvent.touches[0].pageY-begin; up = yaw < 0; swipe = Math.abs(yaw) > 30; }); gate.one('touchend', function() { digit.off('touchmove'); if (swipe && !isBusy()) nextNumber(); }); }) .on('mousedown touchstart', function(e) { if (e.which && e.which != 1) return; var zest = this, item = $(this).index(); $(this).one('mouseup touchend', function(e) { digit.off('mouseup'); var quit = e.originalEvent.changedTouches; if (quit) var jab = document.elementFromPoint(quit[0].clientX, quit[0].clientY); if (swipe || item == 2 || quit && jab != zest || isBusy()) return; up = item == 1; nextNumber(); }); return false; }) .mouseleave(function() { digit.off('mouseup'); }); } function isBusy() { return cog.is(':animated'); } function nextNumber() { var aim = base; swipe = false; up ? aim += slot : aim -= slot; cog.animate({scrollTop: aim}, 250, function() { up ? digit.eq(0).appendTo(cog) : digit.eq(9).prependTo(cog); digit = cog.find('span'); cog.scrollTop(base); field.text(digit.eq(2).text()); }); } }); 
 body { background: grey; } #ticker { width: 150px; text-align: center; margin: auto; } #rotator { height: 140px; font-family: "Times New Roman"; font-size: 50px; line-height: 70px; background-image: url(http://ataredo.com/external/image/flip.png), url(http://ataredo.com/external/image/flip.png), url(http://ataredo.com/external/image/flip.png); background-position: 0 0, 50% 50%, 100% 150%; background-size: 300% 50%; background-repeat: no-repeat; margin: 0 0 10px; overflow: hidden; opacity: 0; } #rotator span { width: 100%; height: 50%; display: inline-block; cursor: default; -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-tap-highlight-color: transparent; } #result { height: 30px; font-size: 20px; color: white; line-height: 30px; letter-spacing: 3px; -webkit-box-shadow: 0 0 3px black; box-shadow: 0 0 3px black; } 
 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.js"></script> <div id="ticker"> <div id="rotator"> <span>8</span> <span>9</span> <span>0</span> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span> </div> <div id="result"></div> </div> 

Pretty straightforward, animates the scroll position up or down and then appends or prepends the first or last number depending on the direction. The duration of the animation can be set here :

cog.animate({scrollTop: current}, 250, function() {

Updated - after having some new insights, for example the touchend event always firing on the original element, I've overhauled the code. Besides that, it now has a sprite background that will stay in proportion with the size of the numbers themselves. Also improved the overall logic and removed a nested listener glitch.

Another reason for this edit is to insert a demo that allows to have multiple tickers (and preset the numbers). As I've even moved on beyond that (adding direct response functionality), I thought it would be a good idea to leave the minimal working code for that here as well:

 $(function() { var gate = $(window), orb = document, cog = $('.rotator'), field = $('#result'), slot = cog.height()/2, base = 1.5*slot, list = [], niche = [7,7,7], term = 250, // duration of animation mass, up = true, yaw = 'mousemove.ambit touchmove.ambit', hike = 'mouseup.turf touchend.turf', part = 'mouseleave.range'; tallyCells(); if (orb.readyState == 'complete') interAction(); else gate.one('load', interAction); gate.on('mouseleave touchcancel', function(e) { !(e.type == 'mouseleave' && e.relatedTarget) && lotRelease(); }); function interAction() { cog.scrollTop(base).each(function(unit) { var pinion = $(this), digit = pinion.find('.quota'), cipher = Number(niche[unit])%10 || 0; list[unit] = digit; niche[unit] = 0; field.append(0); for (var i = 0; i < cipher; i++) nextNumber(pinion, unit, true); pinion.mousewheel(function(turn, delta) { if (isBusy(pinion)) return false; up = delta > 0; nextNumber(pinion, unit); return false; }); digit.on('mousedown touchstart', function(e) { if (e.which && e.which != 1) return; var zest = this, ken = {}, item = $(this).index(); tagPoints(e, ken); digit.on(part, wipeSlate).on(hike, function(e) { wipeSlate(); var quit = e.originalEvent.changedTouches; if (quit) var jab = orb.elementFromPoint(quit[0].clientX, quit[0].clientY); if (item == 2 || quit && jab != zest || isBusy(pinion)) return; up = item == 1; nextNumber(pinion, unit); }); gate.on(yaw, function(e) { hubTrace(e, ken); }) .on(hike, function() { lotRelease(); if (!ken.hit || isBusy(pinion)) return; up = ken.way < 0; nextNumber(pinion, unit); }); return false; }); }).fadeTo(0,1); function tagPoints(act, bit) { var nod = act.originalEvent.touches; bit.mark = nod ? nod[0].pageY : act.pageY; bit.veer = false; } function hubTrace(task, gob) { var peg = task.originalEvent.touches, fly = peg ? peg[0].pageY : task.pageY; gob.way = fly-gob.mark; gob.hit = Math.abs(gob.way) > 30; if (!gob.veer && gob.hit) { gob.veer = true; wipeSlate(); } } function wipeSlate() { mass.off(part + ' ' + hike); } function isBusy(whirl) { return whirl.is(':animated'); } function nextNumber(aim, knob, quick) { var intent = base, hook = list[knob]; up ? intent += slot : intent -= slot; if (quick) { aim.scrollTop(intent); revolveTooth(); } else aim.animate({scrollTop: intent}, term, revolveTooth); function revolveTooth() { up ? hook.eq(0).appendTo(aim) : hook.eq(9).prependTo(aim); list[knob] = aim.find('.quota'); niche[knob] = Number(list[knob].eq(2).text()); aim.scrollTop(base); field.text(niche.join('')); } } } function lotRelease() { gate.off(yaw).add(mass).off(hike); mass.off(part); } function tallyCells() { cog.each(function() { for (var i = 0; i < 10; i++) { var n; !i ? n = 8 : (i == 1 ? n = 9 : n = i-2); $(this).append('<div></div>').find('div').eq(i).text(n).addClass('quota'); } }); mass = $('.quota'); } }); 
 body { text-align: center; background: grey; } #ticker, .rotator { display: inline-block; } .rotator { width: 100px; height: 140px; font-family: "Times New Roman"; font-size: 50px; line-height: 80px; background-image: url(http://ataredo.com/external/image/flip.png), url(http://ataredo.com/external/image/flip.png), url(http://ataredo.com/external/image/flip.png); background-position: 0 0, 50% 50%, 100% 150%; background-size: 300% 50%; background-repeat: no-repeat; margin: 0 0 10px; overflow: hidden; opacity: 0; } .quota { height: 50%; cursor: default; -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-tap-highlight-color: transparent; } #result { height: 35px; font-size: 20px; color: white; line-height: 35px; letter-spacing: 3px; -webkit-box-shadow: 0 0 3px black; box-shadow: 0 0 3px black; } 
 <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.js"></script> <div id="ticker"> <div class="rotator"></div> <div class="rotator"></div> <div class="rotator"></div> <div id="result"></div> </div> 

It will populate the numbers automatically so there's no need to write the markup. Responds to mouse dragging as well.


The latest evolution of the script can be found here :

codepen.io/Shikkediel/pen/avVJdG


Final update - a 3d version that uses transition instead of jQuery .animate . The wheel is made up of individually rotated elements around the x-axis, creating a basically infinite decagon without the need for prepending or appending elements:

codepen.io/Shikkediel/pen/qpjGyq

The cogs are "flickable", making them progress at the speed that the user gives them - then stop again when clicking. They also respond much quicker to mouse wheel events than the original demo. Both reasons why I've left out click events, as opposed to the earlier scripts. Browser support is also a bit more limited but good nonetheless - I've made an extra effort to make it IE10+ compatible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM