Pen Settings



CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource


Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource


Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.


Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.


                <div id='container' style='margin-top:5%; margin-left: 50%'>
  <div id='test'>




                ////////////////// Utility Functions
const True = true, False = false, Zero = 0, emptyBounds = { width: Zero, height: Zero }, λ = function () { return this; };
const callbackIfFunction = function callbackIfFunction(f, r, s) {
            try { return f && ? || f, r) : r; }
            catch (e) { return e; }
const compose = function compose() {
            const e = arguments.length;
            let r = e > 0 ? arguments[0] : undefined;
            for (let i = 0; i < e; ++i) r = callbackIfFunction(arguments[i], r);
            return r;
const _isNullOrUndefined = function _isNullOrUndefined(w) { return w === null | w === undefined; };
    const isNullOrUndefined = function isNullOrUndefined(w) { return 1 === _isNullOrUndefined(w); } //0 !==
    const isNullOrUndefinedOrEmpty = function isNullOrUndefinedOrEmpty(w) { //Todo, would benefit from typeCode / hash of toString
        if (isNullOrUndefined(w)) return True;
        //| forces number
        switch (w.length | w.size)
            //Empty function or Other.
            case 0: return w.charAt ? True : undefined === (w.getYear||w.toFixed||w.keyFor); //Empty function or array or ...
            default: return w.charAt ? emptyStringTest.test(w) : False;
    const isNullOrUndefinedOrEmptyOrEqual = function isNullOrUndefinedOrEmptyOrEqual(w, x) {
        return w === x || isNullOrUndefinedOrEmpty(w);
    //Used to indicate if Any w === r ? false : true
    const AllOf = function AllOf(w, r) {
        r = r || True;
        if (isNullOrUndefined(w)) return False;
        const l = w.length;
        for (let i = 0; i < l; ++i)
            if (isNullOrUndefined(w[i]) === r) return False;
        return True;
    //Todo, should pass optional vaule for w
    const NoneOf = function NoneOf(w) { return AllOf(w, False); };
    const AnyOf = function AnyOf(w) { return False === AllOf(w, False) }
    const FirstOf = function FirstOf(w) { return isNullOrUndefinedOrEmpty(w) ? undefined : w[0]; };
    const LastOf = function LastOf(w) { return isNullOrUndefinedOrEmpty(w) ? undefined : w[w.length - 1]; };


//Default options
        const defaultOptions = {
            //If used with a parent SVG otherwise one will be created
            parent: undefined,
            //Called when disabled
            disable: undefined,
            //Called when enabled
            enable: undefined,
            //Called when dragging
            input: undefined,
            //Called when dragging ends
            change: undefined,
            //Indicates the default state.
            disabled: false,
            //Immediate render
            render: true,
            minValue: 0,
            maxValue: 4,
            container: {
                element: undefined,
                width: '100%',
                height: '100%'
            track: {
                type: 'rect',
                props: {
                    attr: {
                        fill: '#209cee',
                        stroke: 'none',
                        width: 15,
                        height: 110,
                        x: 0,
                        y: 0,
                        rx: 15,
            handle: {
                type: 'circle',
                props: {
                    attr: {
                        fill: '#209cee',
                        stroke: 'none',
                        'stroke-width': 0,
                        'stroke-miterlimit': 10,
                        cx: 7.5, //defaultOptions.track.width * 2,
                        cy: 3, //defaultOptions.track.height * 2,
                        r: 25

         * Constructor passed options which are defaulted if not provided.
         * @param {any} options
        function SvgRange(options) {            
            this.options = this.options || {};

            //Assign from defaultOptions what options are needed
            Object.assign(this.options, defaultOptions);

            //Overwrite with any provided options
            Object.assign(this.options, options);

            //Create the hidden input which will be useful for screen readers
            this.input = document.createElement('input');
            this.input.setAttribute('type', 'range');
   = 'none';
            this.input.setAttribute('aria-hidden', false);
            this.input.setAttribute('min', this.options.minValue);
            this.input.setAttribute('max', this.options.minValue);

            //Alias the value
            this.value = this.input.value;

            //If a parent svg was specified then use that.
            //if (this.options.parent && this.options.parent.tagName.toUpperCase() === 'SVG') {
            if (this.options.parent instanceof SVGElement) {
                this.options.container.element = this.rangeContainer = this.options.parent;
            } else { //Create the SVG which will contain the custom range
                this.rangeContainer = document.createElementNS('', 'svg');
                this.rangeContainer.setAttribute('xmlns', '');
                this.rangeContainer.setAttributeNS('', 'xmlns:xlink', '');
                this.rangeContainer.setAttributeNS('', 'xmlns:xhtml', '');
                this.rangeContainer.setAttribute('width', this.options.container.width);
                this.rangeContainer.setAttribute('height', this.options.container.height);
       = + '_rangeContainer';
       = 'visible';

            //Create the group for all the objects
            this.rangeGroup = document.createElementNS('', 'g');
   = + '_rangeGroup';

            //Add the rangeGroup to the rangeContainer

            //Create the track
            this.track = document.createElementNS('', this.options.track.type);
   = + '_track';            
            TweenMax.set(this.track, this.options.track.props);

            //Add the track to the rangeGroup

            //Create a group for the handles
            this.handleGroup = document.createElementNS('', 'g');
   = + '_handleGroup';

            //Add the handleGroup to the rangeGroup

            this.handle = document.createElementNS('', this.options.handle.type);
   = + '_handle';
            TweenMax.set(this.handle, this.options.handle.props);

            //Fill would be set to an image if needed, or if dragged then this object is already here.
            this.handleBackground = document.createElementNS('', this.options.handle.type);
   = + '_handleBackground';
            TweenMax.set(this.handleBackground, this.options.handle.props);

            //Add the handles to the handleGroup

            this.dragger = this.draggers = undefined;

            //If allowed, render
            if (this.options.render) this.render();

        //Alias the defaultOptions
        SvgRange.defaultOptions = defaultOptions;

         //Ensures the rangeContainer and hidden range input is placed in the container specified by options.
        //Calls enable or disable based on options.disabled
        SvgRange.prototype.render = function render(force) {
            if (isNullOrUndefined(this.options.container.element) || this.rendered && !force) return;

            //Todo, if the node is svg then it must go into it's parrent..
            //Ensure the hidden input is in the container

            //Ensure the Svg Graphic is in the container
            if (this.options.container.element !== this.rangeContainer)

            //Set the value to minValue by default.
            this.value = this.input.value = this.options.minValue;

            //Create the draggers on the handle group, because this inserted the element must be in the dom..
            if (isNullOrUndefinedOrEmpty(this.draggers)) {
                this.draggers = Draggable.create(this.handleGroup, {
                    type: 'y',
                    bounds: { //this.track, would cause transforms from that elements positions..
                        minY: 0,
                        maxY: this.track.height.baseVal.value
                    liveSnap: true,
                    //Direction specified to prevent movement along the other axis, on orientation change this must be changed to match.
                    snap: { y: [] },
                    //throwProps: true,
                    minDuration: 1,
                    overshootTolerance: 0,
                    dragClickables: true,
                    callbackScope: this,
                    onPress: this.onDrag,
                    onDrag: this.onDrag,
                    onThrowUpdate: this.onDrag,
                    onThrowComplete: this.onDragEnd,
                    onDragEnd: this.onDragEnd,
                    onRelease: this.onDragEnd

                //scope the dragger.
                this.dragger = this.draggers[0];

            //Set the snaps.

            //Raise enable or disabled based on the options
            if (this.options.disabled) this.disable();
            else this.enable();
            //Set the value if needed
            if (this.value !== this.options.minValue) this.setValue(this.value);

            this.rendered = true;

        //Called when dragging occurs
        SvgRange.prototype.onDrag = function onDrag() {
//Todo, if throwing then only fire when the value changes because sometimes throw can be called more than once for the same value.
            if (this.dragger.isThrowing) return;
            const value = this.dragger.vars.snapIndex ? this.dragger.vars.snapIndex : this.dragger.vars.snap.y.indexOf(this.dragger.y);
            // Recycle the arguments and call delayed if you don't want to block the event
            //arguments[0] = value; arguments.length = 1;
            //TweenMax.delayedCall(0, this.options.input, arguments)
            callbackIfFunction(this.options.input, value);

        //Called when dragging ends
        SvgRange.prototype.onDragEnd = function onDragEnd() {
            if (this.dragger.isDragging | this.dragger.isThrowing | this.dragger.isPressed) {
            const value = this.dragger.vars.snapIndex ? this.dragger.vars.snapIndex : this.dragger.vars.snap.y.indexOf(this.dragger.y);

        //Called to reset and add any required snaps based on options.maxValue
        SvgRange.prototype.setSnaps = function setSnaps() {
            //Calulcate the spacing, 2 by default if infinite
            const end = (Number.isFinite(this.options.maxValue) ? this.options.maxValue : 2),
                mean = this.track.height.baseVal.value / end;

            //Erase any existing points, todo, this should be on the array of axis which corresponds unless the dragger in uniform in both directions
            this.dragger.vars.snap.y.length = 0;

            //Put the snaps in the dragger at the calculated space.
            for (let i = end; i >= 0; --i)
                this.dragger.vars.snap.y.push(mean * i);

            //this.dragger.applyBounds(); / update

        //Disable dragging, call options.disable
        SvgRange.prototype.disable = function disable() {
            if (isNullOrUndefined(this.dragger) || false === this.dragger.enabled()) return;
            this.input.setAttribute('disabled', 'disabled');

        //Indicates if the dragger is enabled
        SvgRange.prototype.enabled = function enabled() {
            return this.dragger && this.dragger.enabled();

        //Enable dragging, call options.enable
        SvgRange.prototype.enable = function enable() {
            if (isNullOrUndefined(this.dragger) || this.dragger.enabled()) return;

        //Sets the value of this control and the corresponding input
        //Will set the value and optionally the handle positions
        SvgRange.prototype.setValue = function setValue(value, updateHandle) {
            //if (value === this.value) return;
            //If a value was given
            if (false === isNullOrUndefined(value)) {
                if (this.options.minValue && value < this.options.minValue) return;
                if (this.options.maxValue && value > this.options.maxValue) return;
            } else //Determine the value from the position of the dragger
                value = this.dragger.vars.snapIndex ? this.dragger.vars.snapIndex : this.dragger.vars.snap.y.indexOf(this.dragger.y);
            //Set the position based on the snaps if not specifed or truthy
            if(isNullOrUndefined(updateHandle) || updateHandle) TweenMax.set(this.handleGroup, { y: this.dragger.vars.snap.y[value] });
            this.value = this.input.value = value;
          // Recycle the arguments and call delayed if you don't want to block the event
            //arguments[0] = value; arguments.length = 1;
            //TweenMax.delayedCall(0, this.options.change, arguments)
            callbackIfFunction(this.options.change, this.value);            
            //dispatch change on input manually?

        //Sets the minValue in the input and options
        SvgRange.prototype.setMinValue = function setMinValue(value) {
            if (value === this.options.minValue) return;
            this.options.minValue = value;
            this.input.setAttribute('min', value);
            if (this.value < this.options.minValue) this.setValue(this.options.minValue);

        //Sets the maxValue in input and options
        SvgRange.prototype.setMaxValue = function setMaxValue(value) {
            if (value === this.options.maxValue) return;
            this.options.maxValue = value;
            this.input.setAttribute('max', value);
            if (this.value > this.options.maxValue) this.setValue(this.options.maxValue);

//Create a range
const range1 = new SvgRange();
//Set it's container
range1.options.container.element = document.getElementById('test');
//Handle input
range1.options.input = function(){, 0.5, {opacity: 0.5});
//Handle change
range1.options.change = function(){, 0.5, {opacity: 1});
//Render the range into the container
//Make another range with min, maxValues
const range2 = new SvgRange({minValue: 0, maxValue: 1});
//Set the container
range2.options.container.element = range1.options.container.element;
//Set the value