diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts
index 9c48d0211022b739dd37f341c37c281916797ba5..7fd63907dece9095c4b802fc5b12bea60d17f327 100644
--- a/src/mol-canvas3d/camera.ts
+++ b/src/mol-canvas3d/camera.ts
@@ -97,6 +97,7 @@ class Camera implements Object3D {
         Vec3.setMagnitude(this.deltaDirection, this.state.direction, deltaDistance)
         if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection)
         Vec3.add(this.newPosition, this.state.position, this.deltaDirection)
+        
         this.setState({ target, position: this.newPosition })
     }
 
diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index f164a4edb4ab97aa5dc3c49ca8b6e64fc3c9be40..6cee297115311fc3d6d6f0abe9ba808fa4aa3ab5 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -35,11 +35,8 @@ export const Canvas3DParams = {
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
     backgroundColor: PD.Color(Color(0x000000)),
     // TODO: make this an interval?
-    clipNear: PD.Numeric(1, { min: 1, max: 100, step: 1 }),
-    clipFar: PD.Numeric(100, { min: 1, max: 100, step: 1 }),
-    // TODO: make this an interval?
-    fogNear: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
-    fogFar: PD.Numeric(100, { min: 1, max: 100, step: 1 }),
+    clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
+    fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
     pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
     showBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }),
     // debug: PD.Group({
@@ -168,13 +165,13 @@ namespace Canvas3D {
             const cDist = Vec3.distance(camera.state.position, camera.state.target)
             const bRadius = Math.max(10, scene.boundingSphere.radius)
 
-            const nearFactor = (50 - p.clipNear) / 50
-            const farFactor = -(50 - p.clipFar) / 50
+            const nearFactor = (50 - p.clip[0]) / 50
+            const farFactor = -(50 - p.clip[1]) / 50
             const near = cDist - (bRadius * nearFactor)
             const far = cDist + (bRadius * farFactor)
 
-            const fogNearFactor = (50 - p.fogNear) / 50
-            const fogFarFactor = -(50 - p.fogFar) / 50
+            const fogNearFactor = (50 - p.fog[0]) / 50
+            const fogFarFactor = -(50 - p.fog[1]) / 50
             const fogNear = cDist - (bRadius * fogNearFactor)
             const fogFar = cDist + (bRadius * fogFarFactor)
 
@@ -378,10 +375,8 @@ namespace Canvas3D {
                     renderer.setClearColor(props.backgroundColor)
                 }
 
-                if (props.clipNear !== undefined) p.clipNear = props.clipNear
-                if (props.clipFar !== undefined) p.clipFar = props.clipFar
-                if (props.fogNear !== undefined) p.fogNear = props.fogNear
-                if (props.fogFar !== undefined) p.fogFar = props.fogFar
+                if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]]
+                if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]]
 
                 if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== renderer.props.pickingAlphaThreshold) {
                     renderer.setPickingAlphaThreshold(props.pickingAlphaThreshold)
@@ -396,10 +391,8 @@ namespace Canvas3D {
                 return {
                     cameraMode: camera.state.mode,
                     backgroundColor: renderer.props.clearColor,
-                    clipNear: p.clipNear,
-                    clipFar: p.clipFar,
-                    fogNear: p.fogNear,
-                    fogFar: p.fogFar,
+                    clip: p.clip,
+                    fog: p.fog,
                     pickingAlphaThreshold: renderer.props.pickingAlphaThreshold,
                     showBoundingSpheres: boundingSphereHelper.visible
                 }
diff --git a/src/mol-plugin/skin/base/components/controls.scss b/src/mol-plugin/skin/base/components/controls.scss
index 835a585261bff9f9637062e21a414a1154ea924c..31474759cc641cf90c6d4057bd704b551ec518a3 100644
--- a/src/mol-plugin/skin/base/components/controls.scss
+++ b/src/mol-plugin/skin/base/components/controls.scss
@@ -110,6 +110,57 @@
     // }
 }
 
+.msp-slider2 {
+    > div:first-child {
+        position: absolute;
+        height: $row-height;
+        line-height: $row-height;
+        text-align: center;
+        left: 0;
+        width: 25px;
+        top: 0;
+        bottom: 0;
+        font-size: 80%;
+    }
+    > div:nth-child(2) {
+        position: absolute;
+        top: 0;
+        left: 0;
+        bottom: 0;
+        right: 25px;
+        width: 100%;
+        padding-left: 20px;
+        padding-right: 25px;
+        display: table;
+        
+        > div {
+            height: $row-height;
+            display: table-cell;
+            vertical-align: middle;
+            padding: 0 ($control-spacing + 4px);
+        }
+    }
+    > div:last-child {
+        position: absolute;
+        height: $row-height;
+        line-height: $row-height;
+        text-align: center;
+        right: 0;
+        width: 25px;
+        top: 0;
+        bottom: 0;
+        font-size: 80%;
+    }
+    
+    // input[type=text] {
+    //     text-align: right;
+    // }
+    
+    // input[type=range] {
+    //     width: 100%;
+    // }
+}
+
 .msp-toggle-color-picker {
     button {
         border: $control-spacing solid $msp-form-control-background !important;
diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx
index 9c3b656d372e9c97103430d042d72a945fa9f8a5..47e0abc1ab1274938c33a50680dc84dc78652889 100644
--- a/src/mol-plugin/ui/controls/parameters.tsx
+++ b/src/mol-plugin/ui/controls/parameters.tsx
@@ -10,7 +10,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { camelCaseToWords } from 'mol-util/string';
 import { ColorNames } from 'mol-util/color/tables';
 import { Color } from 'mol-util/color';
-import { Slider } from './slider';
+import { Slider, Slider2 } from './slider';
 
 export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
     params: P,
@@ -49,7 +49,8 @@ function controlFor(param: PD.Any): ParamControl | undefined {
         case 'file': return FileControl;
         case 'select': return SelectControl;
         case 'text': return TextControl;
-        case 'interval': return IntervalControl;
+        case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined'
+        ? BoundedIntervalControl : IntervalControl;
         case 'group': return GroupControl;
         case 'mapped': return MappedControl;
         case 'line-graph': return void 0;
@@ -146,16 +147,20 @@ export class SelectControl extends SimpleParam<PD.Select<any>> {
 }
 
 export class IntervalControl extends SimpleParam<PD.Interval> {
-    // onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
-    //     this.setState({ value: e.target.value });
-    //     this.props.onChange(e.target.value);
-    // }
-
+    onChange = (v: [number, number]) => { this.update(v); }
     renderControl() {
         return <span>interval TODO</span>;
     }
 }
 
+export class BoundedIntervalControl extends SimpleParam<PD.Interval> {
+    onChange = (v: [number, number]) => { this.update(v); }
+    renderControl() {
+        return <Slider2 value={this.props.value} min={this.props.param.min!} max={this.props.param.max!}
+            step={this.props.param.step} onChange={this.onChange} disabled={this.props.isDisabled} />;
+    }
+}
+
 export class ColorControl extends SimpleParam<PD.Color> {
     onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
         this.update(Color(parseInt(e.target.value)));
diff --git a/src/mol-plugin/ui/controls/slider.tsx b/src/mol-plugin/ui/controls/slider.tsx
index 260903bbfa6ffd50c32716303c2ba18448b1cd90..f930a20fe558d0496ba4eeec0689a97ad4bb506d 100644
--- a/src/mol-plugin/ui/controls/slider.tsx
+++ b/src/mol-plugin/ui/controls/slider.tsx
@@ -38,12 +38,12 @@ export class Slider extends React.Component<{
     render() {
         let step = this.props.step;
         if (step === void 0) step = 1;
-        return  <div className='msp-slider'>
-        <div>
+        return <div className='msp-slider'>
             <div>
-            <SliderBase min={this.props.min} max={this.props.max} step={step} value={this.state.current} disabled={this.props.disabled}
-                onBeforeChange={this.begin}
-                onChange={this.updateCurrent as any} onAfterChange={this.end as any} />
+                <div>
+                    <SliderBase min={this.props.min} max={this.props.max} step={step} value={this.state.current} disabled={this.props.disabled}
+                        onBeforeChange={this.begin}
+                        onChange={this.updateCurrent as any} onAfterChange={this.end as any} />
                 </div></div>
             <div>
                 {`${Math.round(100 * this.state.current) / 100}`}
@@ -52,6 +52,54 @@ export class Slider extends React.Component<{
     }
 }
 
+export class Slider2 extends React.Component<{
+    min: number,
+    max: number,
+    value: [number, number],
+    step?: number,
+    onChange: (v: [number, number]) => void,
+    disabled?: boolean
+}, { isChanging: boolean, current: [number, number] }> {
+
+    state = { isChanging: false, current: [0, 1] as [number, number] }
+
+    static getDerivedStateFromProps(props: { value: [number, number] }, state: { isChanging: boolean, current: [number, number] }) {
+        if (state.isChanging || (props.value[0] === state.current[0]) && (props.value[1] === state.current[1])) return null;
+        return { current: props.value };
+    }
+
+    begin = () => {
+        this.setState({ isChanging: true });
+    }
+
+    end = (v: [number, number]) => {
+        this.setState({ isChanging: false });
+        this.props.onChange(v);
+    }
+
+    updateCurrent = (current: [number, number]) => {
+        this.setState({ current });
+    }
+
+    render() {
+        let step = this.props.step;
+        if (step === void 0) step = 1;
+        return <div className='msp-slider2'>
+            <div>
+                {`${Math.round(100 * this.state.current[0]) / 100}`}
+            </div>
+            <div>
+                <div>
+                    <SliderBase min={this.props.min} max={this.props.max} step={step} value={this.state.current} disabled={this.props.disabled}
+                        onBeforeChange={this.begin} onChange={this.updateCurrent as any} onAfterChange={this.end as any} range={true} pushable={true} />
+                </div></div>
+            <div>
+                {`${Math.round(100 * this.state.current[1]) / 100}`}
+            </div>
+        </div>;
+    }
+}
+
 /**
  * The following code was adapted from react-components/slider library.
  *