UNPKG

@sjaakp/knob

Version:

Rotary web control

10 lines 3.05 kB
/* * knob.js - transform <input type="number"> into a rotary control. * Version 1.1.2 * (c) Sjaak Priester, Amsterdam, 2025 MIT license * https://sjaakpriester.nl/software/knob * https://github.com/sjaakp/knob */ function Knob(t){this.input=t,this.element=document.createElement("div"),this.element.className="knob",this.element.id=this.input.id+"-knob",t.replaceWith(this.element),this.mask=document.createElement("div"),this.element.appendChild(this.mask),this.mask.appendChild(this.input),this.ccw=!!this.input.dataset.knob.match(/ccw/),this.flip=!!this.input.dataset.knob.match(/flip/);let e=this.input.dataset.gap;e&&e>=0&&e<=90?this.gap=e*Math.PI/180:this.gap=Math.PI/6,this.min=+(this.input.min||0),this.max=+(this.input.max||100),this.ccw?(this.element.style.setProperty("--a","var(--knob-grey, lightgrey"),this.element.style.setProperty("--b","var(--knob-color, orange")):(this.element.style.setProperty("--a","var(--knob-color, orange"),this.element.style.setProperty("--b","var(--knob-grey, lightgrey")),this.flip||(this.element.style.rotate=".5turn",this.mask.style.rotate=".5turn"),this.onPointerMove=(function(t){let e=Math.atan2(t.clientX-this.x,this.y-t.clientY);this.flip||(e+=Math.PI),e<0&&(e+=2*Math.PI);let i=this.gap/2;e>=i&&e<=2*Math.PI-i&&(this.setArc(e),this.input.value=this.arcToValue(e))}).bind(this),this.onPointerUp=(function(){document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointermove",this.onPointerMove)}).bind(this),this.element.addEventListener("pointerdown",t=>{let e=this.element.getBoundingClientRect();this.x=e.left+e.width/2,this.y=e.top+e.height/2,document.addEventListener("pointerup",this.onPointerUp),document.addEventListener("pointermove",this.onPointerMove)}),this.input.addEventListener("input",t=>{this.setArc(this.valueToArc(t.target.value))}),this.setArc(this.valueToArc(this.input.value))}Knob.prototype={getRange(){return this.max-this.min},arcToValue(t){this.ccw&&(t=2*Math.PI-t),t-=this.gap/2;let e=this.getRange();return Math.round(e*t/(2*Math.PI-this.gap))+this.min},valueToArc(t){let e=this.getRange();if(0===e)return 0;t-=this.min;let i=t*(2*Math.PI-this.gap)/e+this.gap/2;return this.ccw?2*Math.PI-i:i},setValue(t){this.input.value=t,this.setArc(this.valueToArc(t))},setArc(t){let e=this.gap/2,i=2*Math.PI-e;this.element.style.backgroundImage=`conic-gradient(Canvas 0 ${e}rad,var(--a) ${e}rad ${t}rad,var(--b) ${t}rad ${i}rad, Canvas ${i}rad)`}},Knob.init=()=>{let t=document.createElement("style");t.appendChild(document.createTextNode(`.knob{place-content:center;place-items:center;width:var(--knob-diameter,6em);aspect-ratio:1;border-radius:500em; div{place-content:center;place-items:center;width:var(--knob-inner-diameter,75%);aspect-ratio:1;border-radius:500em;background-color:Canvas;user-select:none}} [data-knob]{display:block;max-width:6ch;field-sizing:content;&:focus{outline-width:1px}}`)),document.head.appendChild(t),document.querySelectorAll("[type=number][data-knob]").forEach(t=>{new Knob(t)})},Knob.init();