JSFiddle - React, Tailwind, and code Playground
by Scott Kaye
HTML
<div class="flex">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Libero doloremque modi alias. Qui magnam alias illo iste distinctio eius ut.</p>
<input id="dial-1" class="dial" value="50" />
</div>
<input id="dial-2" class="dial" value="0.89" disabled />
<div class="debug"></div>
SCSS
$carbon: #414042;
$primary: #ff9b00;
input.dial {
display: none;
}
div.dial {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 1 1 100%;
position: relative;
font-family: Sofia Pro;
min-width: 150px;
min-height: 150px;
margin-top: -0.03em;
margin-bottom: -0.1em;
.editor {
font-weight: 700;
color: $carbon;
user-select: none;
text-align: center;
position: relative;
}
.value-container {
font-size: 16%;
border-bottom: 0.05em solid #c1c1c1;
word-wrap: normal;
line-height: 1.4em;
}
.value {
outline: none;
}
.edit-button {
font-size: 9%;
text-transform: uppercase;
margin-top: 5%;
display: flex;
justify-content: center;
align-items: center;
text-decoration: none;
color: $carbon;
overflow: hidden;
transition: opacity 250ms ease;
svg, span {
transition: transform 500ms ease;
}
svg {
width: 1.2em;
height: 1.2em;
margin-right: 0.5em;
color: $primary;
}
}
> svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.bg, .line {
stroke-width: 35;
stroke-linecap: round;
fill: none;
}
.bg {
stroke: #dadada;
}
.line {
stroke: $primary;
}
.handle, .handle-hitbox {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.handle {
fill: #fff;
}
.handle-hitbox {
fill: transparent;
}
}
&.editing {
.editor {
.value-container {
background: #fff;
}
.edit-button {
pointer-events: none;
opacity: 0;
svg, span {
transform: translateY(-110%);
}
}
}
}
&[disabled] {
pointer-events: none;
> svg .line {
stroke: #999;
}
.edit-button svg {
color: #999;
}
}
}
body {
background: #fafafa;
}
.flex {
display: flex;
background: #ddd;
width: 600px;
p {
width: 400px;
...
JavaScript
console.clear();
$(function() {
var baseConfig = {
// Values to snap to.
step: 5,
// Number of decimal places to format the underlying value to
precision: 2,
// Units to display beside the value
unit: "$",
// Where the units should display, relative to the value (left or right)
unitPosition: "left",
// Maximum value the dial will go to
maxValue: 100,
// Initial value, which will override the input's value attribute if set
initialValue: null,
// Map the selected percentage to an output value
mapResult: function(percentage) {
return percentage;
}
};
var draggingEl = null;
var globalListenersActivated = false;
// Snap to the nearest configured interval
function roundDialValue(dial, number) {
var config = dial.data("dial-config");
var realInterval = config.step / config.maxValue;
var halfInterval = realInterval / 2;
number += halfInterval - (number + halfInterval) % realInterval;
return +number.toFixed(config.precision);
}
function clamp(number, min, max) {
if (number > max) number = max;
if (number < min) number = min;
return number;
}
function getOrInitDialData(element) {
var data = element.data("dial-data");
if (typeof data === "undefined") {
// Angle data
// Useful resource on arc calculations: https://www.omnicalculator.com/math/arc-length
var path = element.find("path.line").get(0);
var max = path.getTotalLength();
var angle = max / 100 * 180 / Math.PI; // 100 is the radius of the arc
// SVG position data
var svg = element.find("svg");
var boundingRadius = svg.height() / 3;
var dialOffset = svg.offset();
var dialPos = {
x: dialOffset.left + svg.width() / 2,
...