JSFiddle - React, Tailwind, and code Playground

by rich_harris

HTML

<main></main>

<script id='template' type='text/ractive'>
    <label>
        <input type='range' min='0' max='999' value='{{value}}'/>
        {{value}}
    </label>
    
    <label>
        <input type='number' min='0' max='999' value='{{value}}'/>
        {{value}}
    </label>
    
    <odometer value='{{value}}'/>
</script>

<script id='odometer' type='text/ractive'>
    <wheel value='{{hundreds}}'/>
    <wheel value='{{tens}}'/>
    <wheel value='{{units}}'/>
</script>

<script id='wheel' type='text/ractive'>
    <div class='viewport'>
        <div class='digits' style='top: -{{offset}}%;'>
            <span>9</span>
            <span>8</span>
            <span>7</span>
            <span>6</span>
            <span>5</span>
            <span>4</span>
            <span>3</span>
            <span>2</span>
            <span>1</span>
            <span>0</span>
        </div>
    </div>
</script>

CSS

body {
    font-family: 'Helvetica Neue', arial, sans-serif;
    font-weight: 200;
    color: #353535;
}

h1, h2, h3, h4, h5, h6 {
    font-weight: 200;
}

.error {
    color: #d00;
    font-family: monospace;
}

label {
    display: block;
    margin: 0 0 1em 0;
    font-family: inherit;
    font-size: inherit;
}

.viewport {
    display: inline-block;
    position: relative;
    width: 50px;
    height: 50px;
    overflow: hidden;
}

.viewport > div {
    position: absolute;
    top: 0;
}

.digits {
    width: 50px;
    height: 500px;
}

.digits span {
    display: block;
    width: 50px;
    height: 50px;
    background-color: #333;
    color: white;
    font-family: monospace;
    font-size: 4em;
    line-height: 1;
    text-align: center;
}

JavaScript

var Odometer = Ractive.extend({
    template: '#odometer',
    computed: {
        hundreds: '~~(${value}/100)',
        tens: '~~((${value}%100)/10)',
        units: '${value}%10'
    }
});

var Wheel = Ractive.extend({
    template: '#wheel',
    oninit: function () {
        this.observe( 'value', function ( value ) {
            var offset = this.getOffset( value );
            this.animate( 'offset', offset, {
                duration: 200 + Math.random() * 400,
                easing: 'easeInOut'
            });
        }, { init: false });
        
        this.set( 'offset', this.getOffset( this.get( 'value' ) ) );
    },
    getOffset: function ( value ) {
        return 100 * ( 9 - value );
    }
});

Ractive.components.odometer = Odometer;
Ractive.components.wheel = Wheel;

var ractive = new Ractive({
    el: 'main',
    template: '#template',
    data: {
        value: 123
    }
});