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,
         ...