移动webkit浏览器在JS中有舍入问题吗?

我正在尝试使用移动浏览器上的一些javascript滑块代码调试问题。 它似乎是一个舍入错误,只发生在移动设备上。 以下代码可以在桌面上正常工作(例如Chrome),但在智能手机上的Webkit中查看时,增加按钮无法在滑块上使用更高的值,例如iPhone iOS 5/6,Samsung S2 ICS。

试试这个http://jsfiddle.net/codecowboy/mLpfu/ 。 单击“在移动设备上调试”按钮 – 它与左上角的“运行”按钮直接相邻(您需要登录才能看到此按钮)。 输入在智能手机上的浏览器中生成的URL(最好是iPhone / Android上的webkit浏览器)。

拖动滑块说265,点击增加。 有些值会让你增加,有些则不会。 值越高,问题越严重。

代码使用的是jQuery和noUISlider插件。 按钮点击代码是:

var btnIncrease= document.getElementById("increase"); btnIncrease.addEventListener('click',function(e) { var slider = $("#noUiSlider"); console.log(e); var value = slider.noUiSlider('value')[1]; //the 'value' method returns an array. console.log('value pre move '+value); value = value+1; slider.noUiSlider("move", { knob : 0, to: parseInt(value,10) }); console.log(slider.noUiSlider('value')[1]); }); 

任何人都可以解释是什么导致这个? 这可能是一个Big / Little Endian问题吗? 或者jQuery中的错误?

上面的代码调用了nouislider插件,源代码如下:

 (function( $ ){ $.fn.noUiSlider = function( method, options ) { function neg(a){ return ax] */ 'scale' : [0,100], /* * {start} The starting positions for the handles, mapped to {scale}. (init) * [ARRAY][INT] [y>={scale[0]}, y= {scale[0]} || _l, z < {scale[1]} || _u */ 'to' : 0, /* * {handle} The handle to move. (move) * [MIXED] 0,1,"lower","upper" */ 'handle' : 0, /* * {change} The function to be called on every change. (init) * [FUNCTION] param [STRING]'move type' */ 'change' : '', /* * {end} The function when a handle is no longer being changed. (init) * [FUNCTION] param [STRING]'move type' */ 'end' : '', /* * {step} Whether, and at what intervals, the slider should snap to a new position. Adheres to {scale} (init) * [MIXED] 1){ api.connect.css({'left':api.low.left(),'right':(api.slider.innerWidth()-api.up.left())}); } else { api.low ? api.connect.css({'left':api.low.left(),'right':0}) : api.connect.css({'left':0,'right':(api.slider.innerWidth()-api.up.left())}); } } }, left: function(){ return parseFloat($(this).css('left')); }, call: function( f, t, n ){ if ( typeof(f) == "function" ){ f.call(t, n) } }, bounce: function( api, n, c, handle ){ var go = false; if( handle.is( api.up ) ){ if( api.low && n  api.up.left() ){ n = api.up.left(); go=true; } } if ( n > api.slider.innerWidth() ){ n = api.slider.innerWidth() go=true; } else if( n < 0 ){ n = 0; go=true; } return [n,go]; } }; methods = { init: function(){ return this.each( function(){ /* variables */ var s, slider, api; /* fill them */ slider = $(this).css('position','relative'); api = new Object(); api.options = $.extend( defaults, options ); s = api.options; typeof s.start == 'object' ? 1 : s.start=[s.start]; /* Available elements */ api.slider = slider; api.low = $('
'); api.up = $('
'); api.connect = $('
'); /* Append the middle bar */ s.connect ? api.connect.appendTo(api.slider) : api.connect = false; /* Append the handles */ // legacy rename if(s.knobs){ s.handles=s.knobs; } if ( s.handles === 1 ){ /* This always looks weird: Connect=lower, means activate upper, because the bar connects to 0. */ if ( s.connect === true || s.connect === 'lower' ){ api.low = false; api.up = api.up.appendTo(api.slider); api.handles = [api.up]; } else if ( s.connect === 'upper' || !s.connect ) { api.low = api.low.prependTo(api.slider); api.up = false; api.handles = [api.low]; } } else { api.low = api.low.prependTo(api.slider); api.up = api.up.appendTo(api.slider); api.handles = [api.low, api.up]; } if(api.low){ api.low.left = helpers.left; } if(api.up){ api.up.left = helpers.left; } api.slider.children().css('position','absolute'); $.each( api.handles, function( index ){ $(this).css({ 'left' : helpers.scale(s.start[index],api.options.scale,api.slider.innerWidth()), 'zIndex' : index + 1 }).children().bind(touch?'touchstart.noUi':'mousedown.noUi',functions.start); }); if(s.click){ api.slider.click(functions.click).find('*:not(.noUi-midBar)').click(functions.flse); } helpers.connect(api); /* expose */ api.options=s; api.slider.data('api',api); }); }, move: function(){ var api, bounce, to, handle, scale; api = dup($(this).data('api')); api.options = $.extend( api.options, options ); // rename legacy 'knob' if(api.options.knob){ api.options.handle = api.options.knob; } // flatten out the legacy 'lower/upper' options handle = api.options.handle; handle = api.handles[handle == 'lower' || handle == 0 || typeof handle == 'undefined' ? 0 : 1]; bounce = helpers.bounce(api, helpers.scale(api.options.to, api.options.scale, api.slider.innerWidth()), handle.left(), handle); handle.css('left',bounce[0]); if( (handle.is(api.up) && handle.left() == 0) || (handle.is(api.low) && handle.left() == api.slider.innerWidth()) ){ handle.css('zIndex',parseInt(handle.css('zIndex'))+2); } if(options.save===true){ api.options.scale = options.scale; $(this).data('api',api); } helpers.connect(api); helpers.call(api.options.change, api.slider, 'move'); helpers.call(api.options.end, api.slider, 'move'); }, value: function(){ var val1, val2, api; api = dup($(this).data('api')); api.options = $.extend( api.options, options ); val1 = api.low ? Math.round(helpers.deScale(api.low.left(), api.options.scale, api.slider.innerWidth())) : false; val2 = api.up ? Math.round(helpers.deScale(api.up.left(), api.options.scale, api.slider.innerWidth())) : false; if(options.save){ api.options.scale = options.scale; $(this).data('api',api); } return [val1,val2]; }, api: function(){ return $(this).data('api'); }, disable: function(){ return this.each( function(){ $(this).addClass('disabled'); }); }, enable: function(){ return this.each( function(){ $(this).removeClass('disabled'); }); } }, functions = { start: function( e ){ if(! $(this).parent().parent().hasClass('disabled') ){ e.preventDefault(); $('body').bind( 'selectstart.noUi' , functions.flse); $(this).addClass('noUi-activeHandle'); $(document).bind(touch?'touchmove.noUi':'mousemove.noUi', functions.move); touch?$(this).bind('touchend.noUi',functions.end):$(document).bind('mouseup.noUi', functions.end); } }, move: function( e ){ var a,b,h,api,go = false,handle,bounce; h = $('.noUi-activeHandle'); api = h.parent().parent().data('api'); handle = h.parent().is(api.low) ? api.low : api.up; a = e.pageX - Math.round( api.slider.offset().left ); // if there is no pageX on the event, it is probably touch, so get it there. if(isNaN(a)){ a = e.originalEvent.touches[0].pageX - Math.round( api.slider.offset().left ); } // a = p.nw == New position // b = p.cur == Old position b = handle.left(); bounce = helpers.bounce(api, a, b, handle); a = bounce[0]; go = bounce[1]; if ( api.options.step && !go){ // get values from options var v1 = api.options.scale[0], v2 = api.options.scale[1]; // convert values to [0-X>0] range // edge case: both values negative; if( neg(v2) ){ v2 = abs( v1 - v2 ); v1 = 0; } // handle all values v2 = ( v2 + ( -1 * v1 ) ); // converts step to the new range var con = helpers.scale( api.options.step, [0,v2], api.slider.innerWidth() ); // if the current movement is bigger than step, set to step. if ( Math.abs( b - a ) >= con ){ a = a < b ? b-con : b+con; go = true; } } else { go = true; } if(a===b){ go=false; } if(go){ handle.css('left',a); if( (handle.is(api.up) && handle.left() == 0) || (handle.is(api.low) && handle.left() == api.slider.innerWidth()) ){ handle.css('zIndex',parseInt(handle.css('zIndex'))+2); } helpers.connect(api); helpers.call(api.options.change, api.slider, 'slide'); } }, end: function(){ var handle, api; handle = $('.noUi-activeHandle'); api = handle.parent().parent().data('api'); $(document).add('body').add(handle.removeClass('noUi-activeHandle').parent()).unbind('.noUi'); helpers.call(api.options.end, api.slider, 'slide'); }, click: function( e ){ if(! $(this).hasClass('disabled') ){ var api = $(this).data('api'); var s = api.options; var c = e.pageX - api.slider.offset().left; c = s.step ? roundTo(c,helpers.scale( s.step, s.scale, api.slider.innerWidth() )) : c; if( api.low && api.up ){ c < ((api.low.left()+api.up.left())/2) ? api.low.css("left", c) : api.up.css("left", c); } else { api.handles[0].css('left',c); } helpers.connect(api); helpers.call(s.change, api.slider, 'click'); helpers.call(s.end, api.slider, 'click'); } }, flse: function(){ return false; } } if ( methods[method] ) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'No such method: ' + method ); } }; })( jQuery );

可能的罪魁祸首(关闭源代码)是在move: function( e )

 a = e.pageX - Math.round( api.slider.offset().left ); // if there is no pageX on the event, it is probably touch, so get it there. if(isNaN(a)){ a = e.originalEvent.touches[0].pageX - Math.round( api.slider.offset().left ); } // a = p.nw == New position 

假设所涉及的Math库同样准确,并且api.slider.offset()。left在平台之间没有变化,我会在那时从源代码做一些记录来确定e.pageX /的目的和值。 e.originalEvent.touches[0].pageX

另一种可能性是滑块的左偏移,它刚刚从DOM中拉出( $('.noUi-activeHandle').parent().parent().data('api').slider.offset().left )不同的平台之间的准确性不同。 我猜也记录了。

然而,两者都没有暗示指数在不准确方面增长,因此可能还有其他东西。

javascript确实有问题。 所有数字都是内部浮点,因此是浮点舍入问题。

尝试使用http://silentmatt.com/biginteger/来查看它是否能解决您的问题。

有关更多详细信息,请访问http://blog.greweb.fr/2013/01/be-careful-with-js-numbers/ 。