JSFiddle - React, Tailwind, and code Playground

by Shawn Allen

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/0.5.3/document-register-element.js"></script>
<h1>&lt;x-bar&gt;</h1>
<x-bar min="0" max="100" value="50"></x-bar>
<x-bar min="-100" max="100" value="-50"></x-bar>
<x-bar min="-100" max="100" value="-200"></x-bar>
<x-bar min="-100" max="100" value="50"></x-bar>
<x-bar min="100" max="200" value="150"></x-bar>
<x-bar min="0" max="100" value="150"></x-bar>

<script>
var barData = [
	{min: 0, max: 100, value: 50},
  {min: -100, max: 100, value: -50},
  {min: -100, max: 100, value: -200},
  {min: -100, max: 100, value: 50},
  {min: 100, max: 200, value: 150},
  {min: 0, max: 100, value: 150},
]
</script>

<h1>document.createElement()</h1>
<div id="create-element"></div>
<script>
barData.forEach(function(d) {
	var bar = this.appendChild(document.createElement('x-bar'));
  bar.className = 'animate';
  setTimeout(function() {
    for (var key in d) bar[key] = d[key];
  }, 100);
}, document.querySelector('#create-element'));
</script>

SCSS

h1 {
  font-family: monospace;
  margin: 0 0 .5em 0;
}

x-bar {
  display: block;
  border: 1px solid black;
  height: 1em;
  position: relative;
  margin-bottom: .5em;
  
  .rect {
    background: green;
    height: 100%;
    left: 0;
    right: 100%;
    position: absolute;
  }
  
  &.animate .rect {
    transition: left .2s, right .2s;
  }
  
  &[value^="-"] .rect {
    background: red;
  }
}

JavaScript

(function(exports) {

	var XBar = document.registerElement('x-bar', {
	  prototype: Object.create(
    	HTMLElement.prototype,
      {
      	createdCallback: {value: function() {
        	this.__rect = this.appendChild(document.createElement('div'));
          this.__rect.className = 'rect';
        }},
        
        attachedCallback: {value: function() {
        	['min', 'max', 'value'].forEach(function(attr) {
          	if (this.hasAttribute(attr)) {
            	this[attr] = this.getAttribute(attr);
            }
          }, this);
        }},
        
        attributeChangedCallback: {value: attributeChanged},
        
        min: numericProperty('min', 0, lazyRender),
        max: numericProperty('max', 1, lazyRender),
        value: numericProperty('value', 0, lazyRender)
      }
    )
  });
  
  exports.XBar = XBar;
  
  function lazyRender() {
  	if (!this.__renderId) {
    	var _render = function() {
      	render.call(this);
      	delete this.____renderId;
      }
    	this.__renderId = requestAnimationFrame(_render.bind(this));
    }
  }

  function render() {
    var min = this.min;
    var max = this.max;
    var x = function(v) {
    	return (v - min) / (max - min) * 100;
    };
    var value = Math.max(min, Math.min(this.value, max));
    var left = Math.min(value, Math.max(0, min));
    var right = Math.max(0, value);
  	var rect = this.__rect;
    rect.style.setProperty('left', x(left) + '%');
    rect.style.setProperty('right', 100 - x(right) + '%');
  }
  
  function attributeChanged(attr, previous, value) {
  	switch (attr) {
    	case 'min':
      case 'max':
      case 'value':
      	this[attr] = value;
        break;
    }
  }
  
  function numericProperty(name, defaultValue, change) {
  	var symbol = '__' + name;
  	return {
    	get: function() {
      	return (symbol in this)
        	? this[symbol]
          : (this[symbol] = defaultValue);
      },
      set: function(value) {
      	value = isNaN(value) ? 0 : Number(value);
...