HTML
Beitragsdynamik: <div style="width: 500px; max-width: calc(100% - 20px); margin-left: 10px; margin-top: 40px;"> <div id="range1"></div> </div> <br><br> Einkommen: <div style="width: calc(100% - 100px); margin-left: 40px; margin-top: 40px;"> <div id="range2" class="tooltipVisible"></div> </div> <div id="t"></div>
SCSS
.fprange { $barLineWidth: 4px; $barRadius: 2px; $handleWidth: 24px; $handleHeight: 20px; $handleRadius: 2px; $scalaStepHeight: ($handleHeight / 2 - $barLineWidth / 2); $tooltipBorderWidth: 2px; $colorInactive: #E5E5E5; $colorActive: #62D053; $transitionTime: 200ms; padding: ($handleHeight) 0; margin: -($handleHeight / 2) ($barLineWidth / 2); position: relative; display: inline-block; width: calc(100% - #{$barLineWidth}); &.fprange-transition { .handle, .tooltip, .tooltip .scalaNumbers { transition: left $transitionTime ease-out; } .barFilled, .tooltip .numberMask { transition: width $transitionTime ease-out; } .scalaStep, .scalaStepIntermediate { transition: background-color ($transitionTime / 2) ease-out; transition-delay: ($transitionTime / 2); } } &:not(.fprange-transition) { .tooltip, .tooltip .scalaNumbers { transition: width $transitionTime ease-out, margin-left $transitionTime ease-out; } } $arrowSize: 5px; $arrowBorderSize: ($arrowSize + $barLineWidth) - 1px; .tooltip { position: absolute; border: $tooltipBorderWidth solid $colorActive; border-radius: $handleRadius; height: 23px; background-color: #fff; box-shadow: -1px 0 2px 0 rgba(0, 0, 0, .2); cursor: pointer; top: -($handleHeight + $arrowBorderSize); box-sizing: content-box; z-index: 100; &.fprange-prevent-transition { transition: none !important; } &:before, &:after { content: ""; position: absolute; display: inline-block; width: 0; height: 0; border-width: $arrowSize; border-color: #fff transparent transparent transparent; border-style: solid; bottom: -$arrowSize * 2; left: 50%; margin-left: -$arrowSize; } &:before { border-top-color: $colorActive; border-width: $arrowBorderSize; bottom: -$arrowBorderSize * 2; margin-left: -$arrowBorderSize; } .numberMask { width: 100%; height: 100%; overflow: hidden; } &.tooltipFixed { .tooltipNumber { z-index: -1; visibility: hidden; } } &.tooltipDynamic { .tooltipNumber { position: relative; z-index: 1; color: #000; } } .tooltipNumber { display: inline-block; position: absolute; white-space: nowrap; padding: 2px 0; text-align: center; margin-left: $tooltipBorderWidth * 2; } .scalaNumbers { position: relative; height: 100%; white-space: nowrap; margin-left: -$tooltipBorderWidth; .scalaNumber { display: inline-block; position: absolute; color: #000; padding: 2px 0; text-align: center; transform: translateX(-50%); } } } .handle { box-sizing: border-box; position: absolute; z-index: 1; display: inline-block; width: $handleWidth; height: $handleHeight; background: $colorActive; cursor: pointer; margin-top: -($handleHeight / 2 + $barLineWidth / 2); border-radius: $handleRadius; box-shadow: -1px 0 2px 0 rgba(0, 0, 0, .2); padding: 4px 0; text-align: center; line-height: 0; .grip { display: inline-block; width: 2px; height: 12px; margin-right: 3px; background-color: rgba(0, 0, 0, .1); border-radius: $barRadius; pointer-events: none; &:last-child { margin-right: 0; } } } .bar { height: $barLineWidth; background-color: $colorInactive; width: 100%; .barFilled { background-color: $colorActive; height: $barLineWidth; } } .scala { position: absolute; width: 100%; .scalaStep, .scalaStepIntermediate { border-radius: $barRadius; position: absolute; background-color: $colorInactive; width: $barLineWidth; height: ($scalaStepHeight + $barLineWidth); margin-top: -($scalaStepHeight + $barLineWidth); margin-left: -($barLineWidth / 2); &.active { background-color: $colorActive; } &[data-scala-step="0"] { left: 0; background-color: $colorActive; } &.scalaStep:not([data-scala-step="0"]) { left: 100%; } } .scalaStepIntermediate { width: $barLineWidth / 2; margin-top: -($scalaStepHeight / 2 + $barLineWidth); margin-left: -($barLineWidth / 4); height: ($scalaStepHeight / 2 + $barLineWidth); } } &.tooltipVisible { margin-top: ($handleHeight + $arrowBorderSize + $barLineWidth / 2); } }
Script
var options1 = { min: 0, max: 7, step: 1, tooltip: 'fixed', tooltipFormat: { digits: 1, suffix: '%', decimalSeparator: ',', thousandsSeparator: '.', fixed: false }, scala: true, scalaInterval: 1, value: 0 }, options2 = { min: 0, max: 200000, step: 1000, tooltip: true, tooltipFormat: { digits: 0, suffix: ' €', thousandsSeparator: '.', fixed: true, callback: function(str, val, opts) { if (val == opts.max) { str = '≥ '+str; } return str; } }, scala: true, scalaInterval: 10000, value: 30000 }; !function($) { var FpRange = function($element, options) { this.initialized = false; this.$e = $element.addClass('fprange fprange-transition'); this.options = options; this.set('range', options.range ? options.range : [options.min, options.max], false); this.value = options.value || 0; this.percentage = options.percentage || 0; this.diff = this.max - this.min; this.step = options.step || 1; this.tooltip = options.tooltip || false; this.tooltipHide = typeof options.tooltipHide !== 'undefined' ? !!options.tooltipHide : false; this.tooltipFormat = options.tooltipFormat || {}; this.scala = options.scala || false; this.$scalaSteps = []; this.dragging = false; this.percentage = 0; this.barLineWidth = 4; if (this.percentage) this.set('percentage', this.percentage, false); else this.set('value', this.value, false); if (typeof this.min === 'undefined') this.min = 0; if (typeof this.max === 'undefined') this.max = 100; this.$bar = $('<div class="bar" />'), this.$barFilled = $('<div class="barFilled" />').css('width', '0px'), this.$handle = $('<div class="handle" />'), this.$scala = $('<div class="scala" />'); this.$bar.append(this.$barFilled); this.$handle.append('<div class="grip" /><div class="grip" /><div class="grip" />'); this.$e.append(this.$bar, this.$handle); this.handleWidth = this.$handle.outerWidth(); this.scalaInterval = options.scalaInterval || 0; if (!this.scalaInterval) { this.scalaInterval = this.diff / 10 | 0; if (this.scalaInterval < 1) this.scalaInterval = 1; } this.scalaSteps = this.diff / this.scalaInterval | 0; if (this.scalaSteps > 100) this.scalaSteps = 100; if (this.tooltip) { var $numberMask = $('<div class="numberMask" />'); this.$tooltip = $('<div class="tooltip tooltip'+(this.tooltip === 'fixed' ? 'Fixed' : 'Dynamic')+'" />').append($numberMask); this.$tooltipNumber = $('<div class="tooltipNumber" />'); $numberMask.append(this.$tooltipNumber); this.$e.prepend(this.$tooltip); if (this.tooltip === 'fixed') { this.scalaNumberInterval = options.scalaNumberInterval || this.scalaInterval; var html = '', p, scalaNr, pHandleWidth; this.nrScalaNumber = this.diff / this.scalaNumberInterval; if (this.nrScalaNumber > 100) this.nrScalaNumber = 100; for (var i = 0; i <= this.nrScalaNumber; i++) { p = i / this.nrScalaNumber; scalaNr = this.formatTooltipValue(this.min + this.diff * p, this.tooltipFormat); pHandleWidth = (p - .5) * this.handleWidth; html += '<div class="scalaNumber" style="left: calc('+(+(p * 100).toFixed(6))+(pHandleWidth < 0 ? '% + ' : '% - ')+Math.abs(pHandleWidth)+'px)">'+scalaNr+'</div>'; } this.$scalaNumbers = $('<div class="scalaNumbers" />').width(this.barWidth()).append(html); this.$scalaNumber = this.$scalaNumbers.find('.scalaNumber'); $numberMask.append(this.$scalaNumbers); } } if (this.scala) { this.$scalaMin = $('<div class="scalaStep" data-scala-step="0" />'); this.$scalaMax = $('<div class="scalaStep" data-scala-step="'+this.scalaSteps+'" />'); this.$scala.append(this.$scalaMin, this.$scalaMax); var html = ''; for (var i = 1; i <= this.scalaSteps - 1; i++) { html += '<div class="scalaStepIntermediate" data-scala-step="'+i+'" style="left: '+(+(i / this.scalaSteps * 100).toFixed(6))+'%" />'; } this.$scala.append(html); this.$scalaSteps = this.$scala.find('.scalaStep, .scalaStepIntermediate'); this.$e.append(this.$scala); } if (this.$e.is(':visible')) this.setHandle(); else this.$handle.css('left', (-this.barLineWidth / 2)+'px'); this.$handle.on('mousedown touchstart', $.proxy(this.dragStart, this)); this.$e.on('mousedown touchstart', $.proxy(this.dragStart, this)); }; $.extend(FpRange.prototype, { constructor: FpRange, init: function() { this.barWidth(); return this.initialized; }, set: function(key, val, setHandle) { switch (key) { case 'value': this.value = val; this.percentage = (this.value - this.min) / this.diff; break; case 'percentage': this.percentage = val / 100; this.value = this.diff * this.percentage + this.min; break; case 'min': case 'max': case 'range': if (key === 'range') { this.min = val[0]; this.max = val[1]; if (this.min > this.max) { var tmp = this.min; this.min = this.max; this.max = tmp; } } else this[key] = val; this.diff = this.max - this.min; if (this.value > this.max) this.set('value', this.max, setHandle); else if (this.value < this.min) this.set('value', this.min, setHandle); break; } if (setHandle) this.setHandle(); }, val: function(setValue) { if (typeof setValue !== 'undefined') { // set value setValue = +setValue; if (setValue < this.min) setValue = this.min; else if (setValue > this.max) setValue = this.max; this.value = setValue; this.percentage = (this.value - this.min) / this.diff; this.setHandle(); } else { // return value return this.value; } }, pVal: function(setPercentage) { if (typeof setPercentage !== 'undefined') { // set percentage setPercentage = +setPercentage; if (setPercentage < 0) setPercentage = 0; else if (setPercentage > 100) setPercentage = 100; this.percentage = setPercentage / 100; this.value = this.diff * this.percentage + this.min; this.setHandle(); } else { // return percentage return this.percentage; } }, dragStart: function(e) { e.originalEvent.preventDefault(); e.originalEvent.stopPropagation(); var dragByHandle = $(e.currentTarget).is('.handle'), ev = e.originalEvent; this.dragging = true; this.offset = this.$e.offset(); this.width = this.$e.width(); this.clickOffsetX = this.handleWidth / 2; if (dragByHandle) { if (typeof ev.offsetX !== 'undefined') { this.clickOffsetX = ev.offsetX; } else if (ev.layerX) { this.clickOffsetX = ev.layerX; } else if (ev.touches && ev.touches[0] && typeof ev.touches[0].clientX !== false) { this.clickOffsetX = ev.touches[0].pageX - this.$handle.offset().left; } } this.valueChanged = false; this.xLimits = [-this.barLineWidth / 2, this.barWidth() - this.handleWidth + this.barLineWidth / 2]; if (dragByHandle) this.$e.removeClass('fprange-transition'); else this.$e.addClass('fprange-transition'); $(window).on('mousemove.fprangeDrag touchmove.fprangeDrag', $.proxy(this.drag, this)) .on('mouseup.fprangeDrag touchend.fprangeDrag', $.proxy(this.dragEnd, this)); if (this.tooltip && this.tooltipHide) this.$tooltip.fadeIn(200); if (!dragByHandle) { this.drag({ triggered: true, events: e }); } }, drag: function(e) { var x, ev = e.events && e.events.originalEvent || e.originalEvent, pageX; if (ev.pageX) pageX = ev.pageX; else { var touch = ev.touches[0]; pageX = touch.pageX; } if (e.triggered) { x = pageX - this.offset.left - this.handleWidth / 2; } else { this.$e.removeClass('fprange-transition'); x = pageX - this.offset.left - this.clickOffsetX; } if (x < this.xLimits[0]) x = this.xLimits[0]; if (x > this.xLimits[1]) x = this.xLimits[1]; var exactPercentage = (x - this.xLimits[0]) / (this.xLimits[1] - this.xLimits[0]), exactValue = this.diff * exactPercentage + this.min, value = Math.round(exactValue / this.step) * this.step, percentage = (value - this.min) / this.diff; if (this.value !== value) { this.percentage = percentage; this.value = value; this.$e.trigger({ type: 'input', value: this.value, percentage: this.percentage }); this.valueChanged = true; } this.setHandle(this.xLimits, exactPercentage); }, dragEnd: function() { $(window).off('.fprangeDrag'); if (this.valueChanged) { this.$e.trigger({ type: 'change', value: this.value, percentage: this.percentage }); } this.$e.addClass('fprange-transition'); this.setHandle(); if (this.tooltip && this.tooltipHide) this.$tooltip.fadeOut(200); }, setHandle: function(xLimits, percentage) { xLimits = xLimits || [-this.barLineWidth / 2, this.barWidth() - this.handleWidth + this.barLineWidth / 2]; if (typeof percentage === 'undefined') percentage = this.percentage; this.$barFilled.width((percentage * 100)+'%'); var handleX = (percentage * (xLimits[1] - xLimits[0]) + xLimits[0]); this.$handle.css('left', handleX+'px'); // tooltip if (this.tooltip) { this.parseTooltip(handleX, xLimits, percentage); } // scala steps if (this.scala) { var pi, e; for (var i = 1; i < this.scalaSteps; i++) { pi = i / this.scalaSteps; e = this.$scalaSteps.filter('[data-scala-step="'+i+'"]'); if (pi < percentage) { e.addClass('active'); } else { e.removeClass('active'); } } } }, formatTooltipValue: function(v) { var decimalSeparator = this.tooltipFormat.decimalSeparator || '.', thousandsSeparator = this.tooltipFormat.thousandsSeparator || ',', prefix = this.tooltipFormat.prefix || '', suffix = this.tooltipFormat.suffix || '', digits = this.tooltipFormat.digits || 0, fixed = this.tooltipFormat.fixed ? true : false, pow = Math.pow(10, digits), rndV = ''+(Math.round(v * pow) / pow); if (fixed && digits) rndV = rndV.toFixed(digits); if (decimalSeparator !== '.') rndV = (''+rndV).replace('.', decimalSeparator); if (v >= 1000) { if (digits && ~rndV.indexOf(decimalSeparator)) { var parts = rndV.split(decimalSeparator); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator); rndV = parts.join(decimalSeparator); } else { rndV = rndV.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator); } } var str = prefix+rndV+suffix; if (this.tooltipFormat.callback) { str = this.tooltipFormat.callback(str, this.value, this.options); } return str; }, barWidth: function() { if (this.$bar.is(':visible')) { if (!this.initialized) this.initialized = true; if (!this.barWidthValue) { this.barWidthValue = this.$bar.width(); if (this.tooltip) { this.parseTooltip(); this.$tooltip.removeClass('fprange-prevent-transition').fadeIn(200); } } return this.barWidthValue; } else { this.barWidthValue = null; if (this.tooltip) this.$tooltip.hide().addClass('fprange-prevent-transition'); return 100; } }, parseTooltip: function(handleX, xLimits, percentage) { xLimits = xLimits || [-this.barLineWidth / 2, this.barWidthValue - this.handleWidth + this.barLineWidth / 2]; if (typeof percentage === 'undefined') percentage = this.percentage; handleX = handleX || (percentage * (xLimits[1] - xLimits[0]) + xLimits[0]); var visible = this.$tooltip.is(':visible'); if (!visible) this.$tooltip.show().css('visibility', 'hidden'); this.$tooltipNumber.text(this.formatTooltipValue(this.value)); var tooltipBorderWidth = 2 * 2, tooltipNumberWidth = this.$tooltipNumber.width() + tooltipBorderWidth * 3, tooltipX = handleX + this.handleWidth / 2; this.$tooltip.css({ left: tooltipX+'px', width: tooltipNumberWidth - tooltipBorderWidth, marginLeft: (-tooltipNumberWidth / 2)+'px' }); if (this.tooltip === 'fixed' && this.$scalaNumbers) { this.$scalaNumbers.css({ left: (-tooltipX - (1 - percentage) * tooltipBorderWidth)+'px', width: this.barWidthValue+'px', marginLeft: (tooltipNumberWidth / 2)+'px' }); } if (!visible) this.$tooltip.css('visibility', 'visible').hide(); }, resize: function() { this.$e.removeClass('fprange-transition'); this.barWidthValue = null; this.setHandle(); } }); $.fn.fprange = function(a, b, c) { if (a === 'init') { var self = this, initFunc = function() { self.each(function() { var $obj = $(self).data('fprange'); if (!$obj.init()) init = false; }); }; if (b) { // async + callback setTimeout(function() { var init = true; initFunc(); b(init); }, 0); } else { // sync + return var init = true; initFunc(); return init; } } else { return this.each(function() { var $this = $(this), $obj = $this.data('fprange'), options = typeof a === 'object' && a; if (!$obj) { $this.data('fprange', ($obj = new FpRange($this, a))); $(window).resize($.proxy($obj.resize, $obj)); } if (typeof a === 'string') { switch (a) { case 'init': return 123; break; case 'val': case 'pVal': $obj[a](b); break; case 'set': $obj[a](b, c); break; } } }); } }; }(window.jQuery); $('#range1').fprange(options1); $('#range2').fprange(options2);