Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

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

Packages

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.

Behavior

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.

HTML

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

CSS

              
                
              
            
!

JS

              
                ////////////////// 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.call ? f.call(s || f, r) : r; }
            catch (e) { return e; }
        };
const compose = function compose() {
            const e = arguments.length;
            let r = e > Zero ? arguments[Zero] : undefined;
            for (let i = Zero; 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 Zero: 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 = Zero; 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[Zero]; };
    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');
            this.input.style.display = 'none';
            this.input.setAttribute('aria-hidden', false);
            this.input.setAttribute('min', this.options.minValue);
            this.input.setAttribute('max', this.options.minValue);
            this.input.id = Date.now();

            //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('http://www.w3.org/2000/svg', 'svg');
                this.rangeContainer.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
                this.rangeContainer.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', 'http://www.w3.org/1999/xlink');
                this.rangeContainer.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
                this.rangeContainer.setAttribute('width', this.options.container.width);
                this.rangeContainer.setAttribute('height', this.options.container.height);
                this.rangeContainer.id = this.input.id + '_rangeContainer';
                this.rangeContainer.style.overflow = 'visible';
            }

            //Create the group for all the objects
            this.rangeGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
            this.rangeGroup.id = this.input.id + '_rangeGroup';

            //Add the rangeGroup to the rangeContainer
            this.rangeContainer.appendChild(this.rangeGroup);  

            //Create the track
            this.track = document.createElementNS('http://www.w3.org/2000/svg', this.options.track.type);
            this.track.id = this.input.id + '_track';            
            TweenMax.set(this.track, this.options.track.props);

            //Add the track to the rangeGroup
            this.rangeGroup.appendChild(this.track);

            //Create a group for the handles
            this.handleGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
            this.handleGroup.id = this.input.id + '_handleGroup';

            //Add the handleGroup to the rangeGroup
            this.rangeGroup.appendChild(this.handleGroup);

            this.handle = document.createElementNS('http://www.w3.org/2000/svg', this.options.handle.type);
            this.handle.id = this.input.id + '_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('http://www.w3.org/2000/svg', this.options.handle.type);
            this.handleBackground.id = this.input.id + '_handleBackground';
            TweenMax.set(this.handleBackground, this.options.handle.props);

            //Add the handles to the handleGroup
            this.handleGroup.appendChild(this.handle);
            this.handleGroup.appendChild(this.handleBackground);

            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
            this.options.container.element.appendChild(this.input);

            //Ensure the Svg Graphic is in the container
            if (this.options.container.element !== this.rangeContainer)
                this.options.container.element.appendChild(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 | 0
                    },
                    liveSnap: true,
                    //Direction specified to prevent movement along the other axis, on orientation change this must be changed to match.
                    snap: this.getSnap,
                    //throwProps: true,
                    mean: Number.isFinite(this.options.maxValue) ? (this.track.height.baseVal.value | 0) / this.options.maxValue : 1,
                    minDuration: 1,
                    overshootTolerance: 0,
                    dragClickables: true,
                    callbackScope: this,                    
                    onPress: this.onDrag,
                    onDrag: this.onDrag,
                    onThrowUpdate: this.onDrag,
                    onThrowComplete: this.onDragEnd,
                    onDragEnd: this.onDragEnd,
                    //may not have been dragged...
                    onRelease: this.onDragEnd
                });

                //scope the dragger.
                this.dragger = this.draggers[0];
            } else {
                //Todo, support x on change.
                this.dragger.vars.type = 'y';
                this.dragger.applyBounds({
                    minY: 0,
                    maxY: this.track.height.baseVal.value | 0
                });
            }

            //Rendered now...
            this.rendered = true;

            //Raise enable or disabled based on the options
            if (this.options.disabled) this.disable();
            else this.enable();

            //If the value needs to be set then set it now (will fire events)
            this.setValue(this.value, true);
        };

        //Called when dragging occurs
        SvgRange.prototype.onDrag = function onDrag() {
 //Todo, if throwing then only fire when the value changes.
            //if (this.dragger.isThrowing) return;

            //If not snapped then it's at the default value.
            const value = this.calculateValue();

            //Todo, might have to store the input value otherwise calling twice

            //TweenMax.delayedCall(0, this.options.input, [value])
            callbackIfFunction(this.options.input, value);
            //dispatch input event on input manually?
           
        };

        //Called when dragging ends
        SvgRange.prototype.onDragEnd = function onDragEnd() {
            if (this.dragger.isDragging | this.dragger.isThrowing | this.dragger.isPressed) {
                this.onDrag();
                return;
            }                   
            this.setValue();
        };
        
        //Disable dragging, call options.disable
        SvgRange.prototype.disable = function disable() {
            if (isNullOrUndefined(this.dragger) || false === this.dragger.enabled()) return;
            this.input.setAttribute('disabled', 'disabled');
            this.dragger.disable();
            callbackIfFunction(this.options.disable);
        };

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

//Called to get the value from the handle position
        SvgRange.prototype.calculateValue = function calculateValue(at) {
            const pos = this.getSnap.call(this.dragger, at || this.dragger[this.dragger.vars.type]),
                max = this.dragger.vars.type === 'x' ? this.dragger.vars.bounds.maxX : this.dragger.vars.bounds.maxY;
            if (pos >= max) return this.options.minValue;
            const min = this.dragger.vars.type === 'x' ? this.dragger.vars.bounds.minX : this.dragger.vars.bounds.minY;
            if (pos <= min) return this.options.maxValue;
            const step = 1;
            const r = Math.floor((max / pos) * step);
            console.log(`calculateValue, ${r}`);
            return r;
        };

        //Called to calulcate the position in the x or y axis for the handle.
        SvgRange.prototype.getSnap = function getSnap(endValue) {
          console.log(`getSnap, ${endValue}`);
            if (false === Number.isFinite(endValue)) return;
            const max = this.vars.type === 'x' ? this.vars.bounds.maxX : this.vars.bounds.maxY;                
            if (endValue >= max) return max;
            const min = this.vars.type === 'x' ? this.vars.bounds.minX : this.vars.bounds.minY;                
            if (endValue <= min) return min;
            //const maxValue = Number.isFinite(this.options.maxValue) ? this.options.maxValue : 1,
            const mean = this.vars.mean; 
            //Deduct 
            //return mean + (endValue - (endValue % mean));
            const r = Math.round(endValue / mean) * mean;
            console.log(`getSnap - ${r}`);
            return r
        };

        //Called to calulcate the position of the handle from a value
        SvgRange.prototype.calculateHandlePosition = function calculateHandlePosition(value) {
            //If the value should be at the lowest point then return the max
            if (value <= this.options.minValue) return this.dragger.vars.type === 'x' ? this.dragger.vars.bounds.maxX : this.dragger.vars.bounds.maxY;                
            //If the value should be at the highest point then return the min
            if (value >= this.options.maxValue) return this.dragger.vars.type === 'x' ? this.dragger.vars.bounds.minX : this.dragger.vars.bounds.minY;                
            const height = this.dragger.vars.type === 'x' ? this.dragger.vars.bounds.maxX : this.dragger.vars.bounds.maxY;
            const r = height - (value * this.dragger.vars.mean);
            console.log(`calculateHandlePosition, ${r}`);
            return r;
        };

        //Enable dragging, call options.enable
        SvgRange.prototype.enable = function enable() {
            if (isNullOrUndefined(this.dragger) || this.dragger.enabled()) return;
            this.input.removeAttribute('disabled');
            this.dragger.enable();
            callbackIfFunction(this.options.enable);
        };

        //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.calculateValue();
            //Set the position based on the snaps.
            if (updateHandle) TweenMax.set(this.handleGroup, { y: this.calculateHandlePosition(value)});
            this.value = this.input.value = value;
            callbackIfFunction(this.options.change, this.value);
            //Todo, dispatch change on input manually?
          // 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)
        };

        //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
function handleInput(){
  TweenMax.to(this.handleGroup, 0.5, {opacity: 0.5});
  console.log('Input' + this.calculateValue());
};

//Bind the function for use with range1 when called
range1.options.input = handleInput.bind(range1);

//Handle change
function handleChange(){
  TweenMax.to(this.handleGroup, 0.5, {opacity: 1});
  console.log('Change' + this.value);
};

//Handle change
range1.options.change = handleChange.bind(range1);
//Render the range into the container
range1.render();

//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;

//Handle change and input
range2.options.input = handleInput.bind(range2);
range2.options.change = handleChange.bind(range2);

//Render
range2.render();
//Set the value and update the handle
range2.setValue(1, true);

//Make another range with min, maxValues
const range3 = new SvgRange({value: 1, minValue: 0, maxValue: 7});

//Set the container
range3.options.container.element = range2.options.container.element;

//Handle change and input
range3.options.input = handleInput.bind(range3);
range3.options.change = handleChange.bind(range3);

//Render
range3.render();

//Set the value update the handle
range3.setValue(3, true);
              
            
!
999px

Console