Last active
June 14, 2020 11:27
-
-
Save b-aleksei/e626e4e8e3f1339f62cb4fa8323fd874 to your computer and use it in GitHub Desktop.
Double-range
This file contains 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
// for IE11 | |
const supportsTemplate = 'content' in document.createElement('template'); | |
// the function takes a template and returns a DOM element | |
const createElement = supportsTemplate | |
? function (html) { | |
const template = document.createElement('template'); | |
template.innerHTML = html; | |
return template.content.firstElementChild; | |
} | |
: function (html) { | |
const div = document.createElement('div'); | |
div.innerHTML = html; | |
return div.firstElementChild; | |
}; | |
export default createElement; |
This file contains 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
import './style.css'; | |
import createElement from './helpers.js'; | |
import tmpl from './template.js'; | |
export default class DoubleRange { | |
constructor (elemPlaceholder) { | |
this.render(elemPlaceholder); | |
this.thumbs = { | |
from: this.elem.querySelector('.double-range__thumb--from'), | |
to: this.elem.querySelector('.double-range__thumb--to') | |
}; | |
this.inputs = { | |
from: this.elem.querySelector('.double-range__input--from'), | |
to: this.elem.querySelector('.double-range__input--to') | |
}; | |
this.values = { | |
from: this.elem.querySelector('.double-range__output--from'), | |
to: this.elem.querySelector('.double-range__output--to') | |
}; | |
this.indicator = { | |
track: this.elem.querySelector('.double-range__track'), | |
startFill: 0, | |
endFill: 0 | |
} | |
this.eventNames = { | |
pointerdown: 'pointerdown', | |
pointerup: 'pointerup', | |
pointermove: 'pointermove' | |
} | |
if (!window.PointerEvent) { | |
this.eventNames.pointerdown = 'mousedown' | |
this.eventNames.pointerup = 'mouseup' | |
this.eventNames.pointermove = 'mousemove' | |
} | |
this.currentKey = null; | |
this.shift = null; | |
this.thumbsWidth = null; | |
// Assume both inputs have equal max values on start | |
this.maxValue = this.inputs.to.max; | |
// Use form to check reset | |
this.form = this.elem.closest('form'); | |
this.setThumbPosition = this.setThumbPosition.bind(this); | |
this.pointerDown = this.pointerDown.bind(this); | |
this.moveThumb = this.moveThumb.bind(this); | |
this.stopDrag = this.stopDrag.bind(this); | |
this.init(); | |
} | |
get rightEdge () { | |
return this.elem.offsetWidth - this.thumbsWidth; | |
} | |
// replace template from js to html | |
render (elemPlaceholder) { | |
this.elem = createElement(tmpl); | |
elemPlaceholder.replaceWith(this.elem); | |
} | |
init () { | |
this.thumbsWidth = this.thumbs.from.offsetWidth; | |
this.addEvents(); | |
this.setThumbPosition(); | |
} | |
setThumbPosition (params = {}) { | |
const { inputKey } = params; | |
const reset = !inputKey; | |
for (const key in this.thumbs) { | |
if (!reset) { | |
if (inputKey !== key) { | |
continue; | |
} | |
} | |
const input = this.inputs[key]; | |
const inputValue = +input.value; | |
const thumb = this.thumbs[key]; | |
const valueElem = this.values[key]; | |
// Glass wall for inputs | |
if (key === 'from') { | |
const compareTo = this.inputs.to; | |
const compareToValue = +compareTo.value; | |
if (inputValue >= compareToValue) { | |
input.value = compareToValue - Number(input.step); | |
} | |
} else { | |
const compareTo = this.inputs.from; | |
const compareToValue = +compareTo.value; | |
if (inputValue <= compareToValue) { | |
input.value = compareToValue + Number(input.step); | |
} | |
} | |
// Set thumb position | |
const value = input.value || input.defaultValue; | |
let left = value / this.maxValue * this.rightEdge; | |
left = left.toFixed() + 'px' | |
thumb.style.left = left; | |
// Set label texts | |
valueElem.style.left = left; | |
valueElem.innerHTML = value; | |
key === 'from' ? this.indicator.startFill = left : this.indicator.endFill = left | |
} | |
// set color between thumbs | |
this.indicator.track.style.backgroundImage = `linear-gradient(90deg, transparent ${this.indicator.startFill}, | |
currentColor ${this.indicator.startFill} ${this.indicator.endFill}, transparent ${this.indicator.endFill})` | |
} | |
// Events | |
addEvents () { | |
for (const key in this.inputs) { | |
const input = this.inputs[key]; | |
const thumb = this.thumbs[key]; | |
thumb.ondragstart = () => false; | |
input.addEventListener('input', () => { | |
this.setThumbPosition({ inputKey: key }); | |
}); | |
} | |
this.elem.addEventListener(this.eventNames.pointerdown, this.pointerDown); | |
window.addEventListener('resize', this.setThumbPosition); | |
this.form.addEventListener('reset', () => { | |
setTimeout(this.setThumbPosition); | |
}); | |
} | |
pointerDown (e) { | |
const key = e.target.dataset.key; | |
if (!key) { | |
return; | |
} | |
e.preventDefault(); | |
const current = this.thumbs[key]; | |
this.currentKey = key; | |
current.setPointerCapture(e.pointerId); | |
this.shift = e.clientX - current.offsetLeft; | |
this.elem.addEventListener(this.eventNames.pointermove, this.moveThumb); | |
this.elem.addEventListener(this.eventNames.pointerup, this.stopDrag); | |
} | |
moveThumb (e) { | |
const left = e.clientX - this.shift; | |
const key = this.currentKey; | |
const value = left / this.rightEdge * this.maxValue; | |
this.inputs[key].value = value; | |
this.inputs[key].focus(); | |
this.inputs[key].dispatchEvent(new Event('input')); | |
} | |
stopDrag () { | |
this.elem.removeEventListener(this.eventNames.pointermove, this.moveThumb); | |
this.elem.removeEventListener(this.eventNames.pointerup, this.stopDrag); | |
} | |
} | |
// Using | |
const doubleRangeElem = document.querySelector('.double-range'); | |
if (doubleRangeElem instanceof HTMLElement) { | |
new DoubleRange(doubleRangeElem); | |
} | |
This file contains 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
const data = { | |
contClass: '', | |
min: 0, | |
max: 500, | |
step: 10, | |
valueMin: 50, | |
valueMax: 400, | |
nameMin: 'value_from', | |
nameMax: 'value_to' | |
}; | |
const createDoubleRange = function (param = {}) { | |
const { contClass, min, max, step, valueMin, valueMax, nameMin, nameMax } = param; | |
return ` | |
<fieldset class="double-range ${contClass}"> | |
<legend class="visually-hidden">Double range</legend> | |
<span | |
class="double-range__track" | |
></span> | |
<label class="double-range__label"> | |
<span | |
class=" | |
double-range__output | |
double-range__output--from"></span> | |
<input | |
class=" | |
double-range__input | |
double-range__input--from" | |
type="range" | |
min="${min}" | |
max="${max}" | |
step="${step}" | |
value="${valueMin}" | |
name="${nameMin}" | |
data-key="from" | |
> | |
<span | |
class=" | |
double-range__thumb | |
double-range__thumb--from" | |
data-key="from" | |
></span> | |
</label> | |
<label class="double-range__label"> | |
<span | |
class=" | |
double-range__output | |
double-range__output--to"></span> | |
<input | |
class=" | |
double-range__input | |
double-range__input--to" | |
type="range" | |
min="${min}" | |
max="${max}" | |
step="${step}" | |
value="${valueMax}" | |
name="${nameMax}" | |
data-key="to" | |
> | |
<span | |
class=" | |
double-range__thumb | |
double-range__thumb--to" | |
data-key="to" | |
></span> | |
</label> | |
</fieldset> | |
`; | |
}; | |
export default createDoubleRange(data); |
This file contains 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
<form action="https://echo.htmlacademy.ru/"> | |
<!-- Double range --> | |
<fieldset class="double-range"> | |
<legend class="visually-hidden">Double range</legend> | |
<span class="double-range__track"></span> | |
<label class="double-range__label"> | |
<span class="double-range__output double-range__output--from" style="left: 20%">200</span> | |
<input class=" double-range__input double-range__input--from" type="range"> | |
<span class="double-range__thumb double-range__thumb--from" style="left: 20%"></span> | |
</label> | |
<label class="double-range__label"> | |
<span class="double-range__output double-range__output--to" style="left: 70%">450</span> | |
<input class="double-range__input double-range__input--to" type="range"> | |
<span class="double-range__thumb double-range__thumb--to" style="left: 70%"></span> | |
</label> | |
</fieldset> | |
<!-- // Double range --> | |
<button type="reset">Reset</button> | |
<button type="submit">Submit</button> | |
</form> |
This file contains 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
.double-range { | |
margin: 0; | |
padding: 0; | |
position: relative; | |
height: 10rem; | |
border: none; | |
} | |
.double-range__track { | |
position: absolute; | |
z-index: 1; | |
top: 0; | |
bottom: 0; | |
margin: auto; | |
width: 100%; | |
height: .45rem; | |
background-color: #DDD; | |
background-image: linear-gradient(to right, transparent 20%, currentColor 20% 70%, transparent 70%); | |
background-position: 2px 0; | |
border-radius: .25rem; | |
pointer-events: none; | |
color: blue; | |
} | |
.double-range__input { | |
position: absolute; | |
height: 1px; | |
width: 100%; | |
opacity: 0; | |
} | |
.double-range__output { | |
position: absolute; | |
text-align: center; | |
bottom: 100%; | |
width: 35px; | |
} | |
.double-range__thumb { | |
position: absolute; | |
z-index: 2; | |
top: 0; | |
bottom: 0; | |
margin: auto; | |
width: 1.5rem; | |
height: 1.5rem; | |
border-radius: 50%; | |
background-image: linear-gradient(teal, turquoise); | |
touch-action: none; | |
cursor: pointer; | |
} | |
.double-range__input:focus + .double-range__thumb { | |
box-shadow: 0 0 0 4px rgba(0, 0, 0, .25); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment