slider.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /**
  2. @Title: layui.slider 滑块
  3. @Author: star1029
  4. @License:MIT
  5. */
  6. layui.define('jquery', function(exports){
  7. "use strict";
  8. var $ = layui.jquery
  9. //外部接口
  10. ,slider = {
  11. config: {}
  12. ,index: layui.slider ? (layui.slider.index + 10000) : 0
  13. //设置全局项
  14. ,set: function(options){
  15. var that = this;
  16. that.config = $.extend({}, that.config, options);
  17. return that;
  18. }
  19. //事件监听
  20. ,on: function(events, callback){
  21. return layui.onevent.call(this, MOD_NAME, events, callback);
  22. }
  23. }
  24. //操作当前实例
  25. ,thisSlider = function(){
  26. var that = this
  27. ,options = that.config;
  28. return {
  29. setValue: function(value, index){ //设置值
  30. return that.slide('set', value, index || 0);
  31. }
  32. ,config: options
  33. }
  34. }
  35. //字符常量
  36. ,MOD_NAME = 'slider', DISABLED = 'layui-disabled', ELEM_VIEW = 'layui-slider', SLIDER_BAR = 'layui-slider-bar', SLIDER_WRAP = 'layui-slider-wrap', SLIDER_WRAP_BTN = 'layui-slider-wrap-btn', SLIDER_TIPS = 'layui-slider-tips', SLIDER_INPUT = 'layui-slider-input', SLIDER_INPUT_TXT = 'layui-slider-input-txt', SLIDER_INPUT_BTN = 'layui-slider-input-btn', ELEM_HOVER = 'layui-slider-hover'
  37. //构造器
  38. ,Class = function(options){
  39. var that = this;
  40. that.index = ++slider.index;
  41. that.config = $.extend({}, that.config, slider.config, options);
  42. that.render();
  43. };
  44. //默认配置
  45. Class.prototype.config = {
  46. type: 'default' //滑块类型,垂直:vertical
  47. ,min: 0 //最小值
  48. ,max: 100 //最大值,默认100
  49. ,value: 0 //初始值,默认为0
  50. ,step: 1 //间隔值
  51. ,showstep: false //间隔点开启
  52. ,tips: true //文字提示,开启
  53. ,input: false //输入框,关闭
  54. ,range: false //范围选择,与输入框不能同时开启,默认关闭
  55. ,height: 200 //配合 type:"vertical" 使用,默认200px
  56. ,disabled: false //滑块禁用,默认关闭
  57. ,theme: '#009688' //主题颜色
  58. };
  59. //滑块渲染
  60. Class.prototype.render = function(){
  61. var that = this
  62. ,options = that.config;
  63. //间隔值不能小于 1
  64. if(options.step < 1) options.step = 1;
  65. //最大值不能小于最小值
  66. if(options.max < options.min) options.max = options.min + options.step;
  67. //判断是否开启双滑块
  68. if(options.range){
  69. options.value = typeof(options.value) == 'object' ? options.value : [options.min, options.value];
  70. var minValue = Math.min(options.value[0], options.value[1])
  71. ,maxValue = Math.max(options.value[0], options.value[1]);
  72. options.value[0] = minValue > options.min ? minValue : options.min;
  73. options.value[1] = maxValue > options.min ? maxValue : options.min;
  74. options.value[0] = options.value[0] > options.max ? options.max : options.value[0];
  75. options.value[1] = options.value[1] > options.max ? options.max : options.value[1];
  76. var scaleFir = Math.floor((options.value[0] - options.min) / (options.max - options.min) * 100)
  77. ,scaleSec = Math.floor((options.value[1] - options.min) / (options.max - options.min) * 100)
  78. ,scale = scaleSec - scaleFir + '%';
  79. scaleFir = scaleFir + '%';
  80. scaleSec = scaleSec + '%';
  81. } else {
  82. //如果初始值是一个数组,则获取数组的最小值
  83. if(typeof options.value == 'object'){
  84. options.value = Math.min.apply(null, options.value);
  85. }
  86. //初始值不能小于最小值且不能大于最大值
  87. if(options.value < options.min) options.value = options.min;
  88. if(options.value > options.max) options.value = options.max;
  89. var scale = Math.floor((options.value - options.min) / (options.max - options.min) * 100) + '%';
  90. };
  91. //如果禁用,颜色为统一的灰色
  92. var theme = options.disabled ? '#c2c2c2' : options.theme;
  93. //滑块
  94. var temp = '<div class="layui-slider '+ (options.type === 'vertical' ? 'layui-slider-vertical' : '') +'">'+ (options.tips ? '<div class="layui-slider-tips"></div>' : '') +
  95. '<div class="layui-slider-bar" style="background:'+ theme +'; '+ (options.type === 'vertical' ? 'height' : 'width') +':'+ scale +';'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ (scaleFir || 0) +';"></div><div class="layui-slider-wrap" style="'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ (scaleFir || scale) +';">' +
  96. '<div class="layui-slider-wrap-btn" style="border: 2px solid '+ theme +';"></div></div>'+ (options.range ? '<div class="layui-slider-wrap" style="'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ scaleSec +';"><div class="layui-slider-wrap-btn" style="border: 2px solid '+ theme +';"></div></div>' : '') +'</div>';
  97. var othis = $(options.elem)
  98. ,hasRender = othis.next('.' + ELEM_VIEW);
  99. //生成替代元素
  100. hasRender[0] && hasRender.remove(); //如果已经渲染,则Rerender
  101. that.elemTemp = $(temp);
  102. //把数据缓存到滑块上
  103. if(options.range){
  104. that.elemTemp.find('.' + SLIDER_WRAP).eq(0).data('value', options.value[0]);
  105. that.elemTemp.find('.' + SLIDER_WRAP).eq(1).data('value', options.value[1]);
  106. }else{
  107. that.elemTemp.find('.' + SLIDER_WRAP).data('value', options.value);
  108. };
  109. //插入替代元素
  110. othis.html(that.elemTemp);
  111. //垂直滑块
  112. if(options.type === 'vertical'){
  113. that.elemTemp.height(options.height + 'px');
  114. };
  115. //显示间断点
  116. if(options.showstep){
  117. var number = (options.max - options.min) / options.step, item = '';
  118. for(var i = 1; i < number + 1; i++) {
  119. var step = i * 100 / number;
  120. if(step < 100){
  121. item += '<div class="layui-slider-step" style="'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ step +'%"></div>'
  122. }
  123. };
  124. that.elemTemp.append(item);
  125. };
  126. //插入输入框
  127. if(options.input && !options.range){
  128. var elemInput = $('<div class="layui-slider-input layui-input"><div class="layui-slider-input-txt"><input type="text" class="layui-input"></div><div class="layui-slider-input-btn"><i class="layui-icon layui-icon-up"></i><i class="layui-icon layui-icon-down"></i></div></div>');
  129. othis.css("position","relative");
  130. othis.append(elemInput);
  131. othis.find('.' + SLIDER_INPUT_TXT).children('input').val(options.value);
  132. if(options.type === 'vertical'){
  133. elemInput.css({
  134. left: 0
  135. ,top: -48
  136. });
  137. } else {
  138. that.elemTemp.css("margin-right", elemInput.outerWidth() + 15);
  139. }
  140. };
  141. //给未禁止的滑块滑动事件
  142. if(!options.disabled){
  143. that.slide();
  144. }else{
  145. that.elemTemp.addClass(DISABLED);
  146. that.elemTemp.find('.' + SLIDER_WRAP_BTN).addClass(DISABLED);
  147. };
  148. //划过滑块显示数值
  149. that.elemTemp.find('.' + SLIDER_WRAP_BTN).on('mouseover', function(){
  150. var sliderWidth = options.type === 'vertical' ? options.height : that.elemTemp[0].offsetWidth
  151. ,sliderWrap = that.elemTemp.find('.' + SLIDER_WRAP)
  152. ,tipsLeft = options.type === 'vertical' ? (sliderWidth - $(this).parent()[0].offsetTop - sliderWrap.height()) : $(this).parent()[0].offsetLeft
  153. ,left = tipsLeft / sliderWidth * 100
  154. ,value = $(this).parent().data('value')
  155. ,tipsTxt = options.setTips ? options.setTips(value) : value;
  156. that.elemTemp.find('.' + SLIDER_TIPS).html(tipsTxt);
  157. if(options.type === 'vertical'){
  158. that.elemTemp.find('.' + SLIDER_TIPS).css({"bottom":left + '%', "margin-bottom":"20px", "display":"inline-block"});
  159. }else{
  160. that.elemTemp.find('.' + SLIDER_TIPS).css({"left":left + '%', "display":"inline-block"});
  161. };
  162. }).on('mouseout', function(){
  163. that.elemTemp.find('.' + SLIDER_TIPS).css("display", "none");
  164. });
  165. };
  166. //滑块滑动
  167. Class.prototype.slide = function(setValue, value, i){
  168. var that = this
  169. ,options = that.config
  170. ,sliderAct = that.elemTemp
  171. ,sliderWidth = function(){
  172. return options.type === 'vertical' ? options.height : sliderAct[0].offsetWidth
  173. }
  174. ,sliderWrap = sliderAct.find('.' + SLIDER_WRAP)
  175. ,sliderTxt = sliderAct.next('.' + SLIDER_INPUT)
  176. ,inputValue = sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').val()
  177. ,step = 100 / ((options.max - options.min) / Math.ceil(options.step))
  178. ,change = function(offsetValue, index){
  179. if(Math.ceil(offsetValue) * step > 100){
  180. offsetValue = Math.ceil(offsetValue) * step
  181. }else{
  182. offsetValue = Math.round(offsetValue) * step
  183. };
  184. offsetValue = offsetValue > 100 ? 100: offsetValue;
  185. sliderWrap.eq(index).css((options.type === 'vertical' ?'bottom':'left'), offsetValue + '%');
  186. var firLeft = valueTo(sliderWrap[0].offsetLeft)
  187. ,secLeft = options.range ? valueTo(sliderWrap[1].offsetLeft) : 0;
  188. if(options.type === 'vertical'){
  189. sliderAct.find('.' + SLIDER_TIPS).css({"bottom":offsetValue + '%', "margin-bottom":"20px"});
  190. firLeft = valueTo(sliderWidth() - sliderWrap[0].offsetTop - sliderWrap.height());
  191. secLeft = options.range ? valueTo(sliderWidth() - sliderWrap[1].offsetTop - sliderWrap.height()) : 0;
  192. }else{
  193. sliderAct.find('.' + SLIDER_TIPS).css("left",offsetValue + '%');
  194. };
  195. firLeft = firLeft > 100 ? 100: firLeft;
  196. secLeft = secLeft > 100 ? 100: secLeft;
  197. var minLeft = Math.min(firLeft, secLeft)
  198. ,wrapWidth = Math.abs(firLeft - secLeft);
  199. if(options.type === 'vertical'){
  200. sliderAct.find('.' + SLIDER_BAR).css({"height":wrapWidth + '%', "bottom":minLeft + '%'});
  201. }else{
  202. sliderAct.find('.' + SLIDER_BAR).css({"width":wrapWidth + '%', "left":minLeft + '%'});
  203. };
  204. var selfValue = options.min + Math.round((options.max - options.min) * offsetValue / 100);
  205. inputValue = selfValue;
  206. sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').val(inputValue);
  207. sliderWrap.eq(index).data('value', selfValue);
  208. selfValue = options.setTips ? options.setTips(selfValue) : selfValue;
  209. sliderAct.find('.' + SLIDER_TIPS).html(selfValue);
  210. //如果开启范围选择,则返回数组值
  211. if(options.range){
  212. var arrValue = [
  213. sliderWrap.eq(0).data('value')
  214. ,sliderWrap.eq(1).data('value')
  215. ];
  216. if(arrValue[0] > arrValue[1]) arrValue.reverse(); //如果前面的圆点超过了后面的圆点值,则调换顺序
  217. }
  218. //回调
  219. options.change && options.change(options.range ? arrValue : selfValue);
  220. }
  221. ,valueTo = function(value){
  222. var oldLeft = value / sliderWidth() * 100 / step
  223. ,left = Math.round(oldLeft) * step;
  224. if(value == sliderWidth()){
  225. left = Math.ceil(oldLeft) * step;
  226. };
  227. return left;
  228. }
  229. //拖拽元素
  230. ,elemMove = $(['<div class="layui-auxiliar-moving" id="LAY-slider-moving"></div'].join(''))
  231. ,createMoveElem = function(move, up){
  232. var upCall = function(){
  233. up && up();
  234. elemMove.remove();
  235. };
  236. $('#LAY-slider-moving')[0] || $('body').append(elemMove);
  237. elemMove.on('mousemove', move);
  238. elemMove.on('mouseup', upCall).on('mouseleave', upCall);
  239. };
  240. //动态赋值
  241. if(setValue === 'set') return change(value, i);
  242. //滑块滑动
  243. sliderAct.find('.' + SLIDER_WRAP_BTN).each(function(index){
  244. var othis = $(this);
  245. othis.on('mousedown', function(e){
  246. e = e || window.event;
  247. var oldleft = othis.parent()[0].offsetLeft
  248. ,oldx = e.clientX;
  249. if(options.type === 'vertical'){
  250. oldleft = sliderWidth() - othis.parent()[0].offsetTop - sliderWrap.height()
  251. oldx = e.clientY;
  252. };
  253. var move = function(e){
  254. e = e || window.event;
  255. var left = oldleft + (options.type === 'vertical' ? (oldx - e.clientY) : (e.clientX - oldx));
  256. if(left < 0)left = 0;
  257. if(left > sliderWidth())left = sliderWidth();
  258. var reaLeft = left / sliderWidth() * 100 / step;
  259. change(reaLeft, index);
  260. othis.addClass(ELEM_HOVER);
  261. sliderAct.find('.' + SLIDER_TIPS).show();
  262. e.preventDefault();
  263. };
  264. var up = function(){
  265. othis.removeClass(ELEM_HOVER);
  266. sliderAct.find('.' + SLIDER_TIPS).hide();
  267. };
  268. createMoveElem(move, up)
  269. });
  270. });
  271. //点击滑块
  272. sliderAct.on('click', function(e){
  273. var main = $('.' + SLIDER_WRAP_BTN);
  274. if(!main.is(event.target) && main.has(event.target).length === 0 && main.length){
  275. var left = options.type === 'vertical' ? (sliderWidth() - e.clientY + $(this).offset().top):(e.clientX - $(this).offset().left), index;
  276. if(left < 0)left = 0;
  277. if(left > sliderWidth())left = sliderWidth();
  278. var reaLeft = left / sliderWidth() * 100 / step;
  279. if(options.range){
  280. if(options.type === 'vertical'){
  281. index = Math.abs(left - parseInt($(sliderWrap[0]).css('bottom'))) > Math.abs(left - parseInt($(sliderWrap[1]).css('bottom'))) ? 1 : 0;
  282. }else{
  283. index = Math.abs(left - sliderWrap[0].offsetLeft) > Math.abs(left - sliderWrap[1].offsetLeft) ? 1 : 0;
  284. }
  285. }else{
  286. index = 0;
  287. };
  288. change(reaLeft, index);
  289. e.preventDefault();
  290. }
  291. });
  292. //输入框移入事件
  293. sliderTxt.hover(function(){
  294. var othis = $(this);
  295. othis.children('.' + SLIDER_INPUT_BTN).fadeIn('fast');
  296. }, function(){
  297. var othis = $(this);
  298. othis.children('.' + SLIDER_INPUT_BTN).fadeOut('fast');
  299. });
  300. //点击加减输入框
  301. sliderTxt.children('.' + SLIDER_INPUT_BTN).children('i').each(function(index){
  302. $(this).on('click', function(){
  303. if(index == 1){
  304. inputValue = inputValue - options.step < options.min
  305. ? options.min
  306. : Number(inputValue) - options.step;
  307. }else{
  308. inputValue = Number(inputValue) + options.step > options.max
  309. ? options.max
  310. : Number(inputValue) + options.step;
  311. };
  312. var inputScale = (inputValue - options.min) / (options.max - options.min) * 100 / step;
  313. change(inputScale, 0);
  314. });
  315. });
  316. //获取输入框值
  317. var getInputValue = function(){
  318. var realValue = this.value;
  319. realValue = isNaN(realValue) ? 0 : realValue;
  320. realValue = realValue < options.min ? options.min : realValue;
  321. realValue = realValue > options.max ? options.max : realValue;
  322. this.value = realValue;
  323. var inputScale = (realValue - options.min) / (options.max - options.min) * 100 / step;
  324. change(inputScale, 0);
  325. };
  326. sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').on('keydown', function(e){
  327. if(e.keyCode === 13){
  328. e.preventDefault();
  329. getInputValue.call(this);
  330. }
  331. }).on('change', getInputValue);
  332. };
  333. //事件处理
  334. Class.prototype.events = function(){
  335. var that = this
  336. ,options = that.config;
  337. };
  338. //核心入口
  339. slider.render = function(options){
  340. var inst = new Class(options);
  341. return thisSlider.call(inst);
  342. };
  343. exports(MOD_NAME, slider);
  344. })