├── README.md └── src └── slider.coffee /README.md: -------------------------------------------------------------------------------- 1 |

2 | icon
3 | Framer Slider
4 |
5 |

6 |
7 | 8 |

9 | banner 10 |
11 |

INTRODUCTION
12 |

From adaptive sliders created in Design to fully functional ones in Code. A Framer module that allows you to design sliders and then bring them to life, without losing customizability. Appearance and logic—separated.

13 |

14 |
15 |
16 | 17 | ## Get Started 18 | First, grab the `slider.coffee` file and place it within the `/modules` folder (located within your `.framer` folder). 19 | Then, to include the module, `require` the `Slider` class: 20 | 21 | ``` 22 | # Include the module 23 | {Slider} = require "slider" 24 | ``` 25 | 26 | The `Slider.wrap` method takes three parameters: 27 | - `background` — The background layer of the slider. 28 | - `fill` — The fill layer of the slider. 29 | - `knob` – The knob layer of the slider. 30 | 31 | All of these can be styled completely in the design. The method wraps a component around these 3 layers to handle all of the sliding functionality for you. 32 | 33 | ``` 34 | # Wrap slider logic 35 | Slider.wrap(background, fill, knob) 36 | ``` 37 | 38 | To customize the Slider, you can store it in a variable. 39 | 40 | ``` 41 | # Wrap slider logic 42 | slider = Slider.wrap(background, fill, knob) 43 | ``` 44 | 45 | This allows you to customize its properties, like the `min`, `max` and `value`. 46 | 47 | ``` 48 | # Set range, default value 49 | slider.props = 50 | min: 0 51 | max: 100 52 | value: 50 53 | ``` 54 | 55 | And finally—to output its values, you can use the `onValueChange` method. 56 | 57 | ``` 58 | # Update value & print output 59 | slider.onValueChange -> 60 | print slider.value 61 | ``` 62 | --- 63 | 64 | ## Examples 65 | 66 | - **[Slider Wrapping](https://framer.cloud/aWnxD).** Simple example with realtime value. 67 | - **[Value Modulating](https://framer.cloud/WJpNi).** Adjust the background color of a layer. 68 | - **[Gradient Sliders](https://framer.cloud/rqJjF/).** Adjust the properties of a linear gradient. 69 | - **[Photo Filters](https://framer.cloud/ervdH).** Adjust saturation, contrast and grayscale filters. 70 | - See the **[Framer Audio](https://github.com/benjamindenboer/FramerAudio)** module for more. 71 | 72 | --- 73 | 74 | ## Resources 75 | - **[SliderComponent Properties](https://framer.com/docs/#slider.slidercomponent)** — Discover all properties. 76 | - **[SliderComponent Events](https://framer.com/docs/#events.value)** — Discover all events. 77 | - **[SliderComponent Guide](https://framer.com/getstarted/guides/code/#slider)** — See the official beginners guide. 78 | - **[Utils.Modulate Docs](https://framer.com/docs/#utils.modulate)** — Learn how to convert value ranges. 79 | 80 | --- 81 | 82 | ## Contact 83 | - Follow me @benjaminnathan. 84 | - Follow @framer. 85 | -------------------------------------------------------------------------------- /src/slider.coffee: -------------------------------------------------------------------------------- 1 | # Framer Slider Module 2 | # By Benjamin den Boer 3 | # Follow me @benjaminnathan 4 | # Follow Framer @framer 5 | 6 | Events.SliderValueChange = "sliderValueChange" 7 | 8 | class exports.Slider extends Layer 9 | 10 | constructor: (options={}) -> 11 | super options 12 | 13 | @_knob = undefined 14 | @_fill = undefined 15 | @_background = undefined 16 | 17 | _touchStart: (event) => 18 | event.preventDefault() 19 | 20 | if @_background.width > @_background.height 21 | touchX = Events.touchEvent(event).clientX - Screen.canvasFrame.x 22 | scaleX = @canvasScaleX() 23 | @value = @valueForPoint(touchX / scaleX - @screenFrame.x) 24 | else 25 | touchY = Events.touchEvent(event).clientY - Screen.canvasFrame.y 26 | scaleY = @canvasScaleY() 27 | @value = @valueForPoint(touchY / scaleY - @screenFrame.y) 28 | 29 | @_knob.draggable._touchStart(event) 30 | @_updateValue() 31 | 32 | _touchEnd: (event) => 33 | @_updateValue() 34 | 35 | _updateFill: => 36 | if @_background.width > @_background.height 37 | @_fill.width = @_knob.midX 38 | else 39 | @_fill.height = @_knob.midY 40 | 41 | _updateKnob: => 42 | if @_background.width > @_background.height 43 | @_knob.midX = @_fill.width 44 | @_knob.centerY() 45 | else 46 | @_knob.midY = @_fill.height 47 | @_knob.centerX() 48 | 49 | _updateFrame: => 50 | @_knob.draggable.constraints = 51 | x: -knob.width / 2 52 | y: -knob.height / 2 53 | width: @_background.width + @_knob.width 54 | height: @_background.height + @_knob.height 55 | 56 | if @constrained 57 | @_knob.draggable.constraints = 58 | x: 0 59 | y: 0 60 | width: @_background.width 61 | height: @_background.height 62 | 63 | if @_background.width > @_background.height 64 | @_fill.height = @_background.height 65 | @_knob.midX = @pointForValue(@value) 66 | @_knob.centerY() 67 | else 68 | @_fill.width = @_background.width 69 | @_knob.midY = @pointForValue(@value) 70 | @_knob.centerX() 71 | 72 | if @_background.width > @_background.height 73 | @_knob.draggable.speedY = 0 74 | @_knob.draggable.speedX = 1 75 | else 76 | @_knob.draggable.speedX = 0 77 | @_knob.draggable.speedY = 1 78 | 79 | addBackgroundLayer: (layer) -> 80 | @_background = layer 81 | @_background.parent = @ 82 | @_background.name = "background" 83 | @_background.x = @_background.y = 0 84 | return @_background 85 | 86 | addFillLayer: (layer) -> 87 | @_fill = layer 88 | @_fill.parent = @ 89 | @_fill.name = "fill" 90 | @_fill.x = @_fill.y = 0 91 | @_fill.width = @width / 2 92 | return @_fill 93 | 94 | addKnobLayer: (layer) -> 95 | @_knob = layer 96 | @_knob.parent = @ 97 | @_knob.name = "knob" 98 | @_knob.draggable.enabled = true 99 | @_knob.draggable.overdrag = false 100 | @_knob.draggable.momentum = true 101 | @_knob.draggable.momentumOptions = {friction: 5, tolerance: 0.25} 102 | @_knob.draggable.bounce = false 103 | @_knob.x = Align.center() 104 | @_knob.y = Align.center() 105 | 106 | return @_knob 107 | 108 | @define "constrained", @simpleProperty("constrained", false) 109 | 110 | @define "min", 111 | get: -> @_min or 0 112 | set: (value) -> 113 | @_min = value if _.isFinite(value) 114 | @emit("change:min", @_min) 115 | 116 | @define "max", 117 | get: -> @_max or 1 118 | set: (value) -> 119 | @_max = value if _.isFinite(value) 120 | @emit("change:max", @_max) 121 | 122 | @define "value", 123 | get: -> return @_value 124 | set: (value) -> 125 | return unless _.isFinite(value) 126 | 127 | @_value = Utils.clamp(value, @min, @max) 128 | 129 | if @_background.width > @_background.height 130 | @_knob.midX = @pointForValue(value) 131 | else 132 | @_knob.midY = @pointForValue(value) 133 | 134 | @_updateFill() 135 | @_updateValue() 136 | 137 | _knobDidMove: => 138 | if @_background.width > @_background.height 139 | @value = @valueForPoint(@_knob.midX) 140 | else 141 | @value = @valueForPoint(@_knob.midY) 142 | 143 | _updateValue: => 144 | return if @_lastUpdatedValue is @value 145 | 146 | @_lastUpdatedValue = @value 147 | @emit("change:value", @value) 148 | @emit(Events.SliderValueChange, @value) 149 | 150 | pointForValue: (value) -> 151 | if @_background.width > @_background.height 152 | if @constrained 153 | return Utils.modulate(value, [@min, @max], [0 + (@_knob.width / 2), @_background.width - (@_knob.width / 2)], true) 154 | else 155 | return Utils.modulate(value, [@min, @max], [0, @_background.width], true) 156 | else 157 | if @constrained 158 | return Utils.modulate(value, [@min, @max], [0 + (@_knob.height / 2), @_background.height - (@_knob.height / 2)], true) 159 | else 160 | return Utils.modulate(value, [@min, @max], [0, @_background.height], true) 161 | 162 | valueForPoint: (value) -> 163 | if @_background.width > @_background.height 164 | if @constrained 165 | return Utils.modulate(value, [0 + (@_knob.width / 2), @_background.width - (@_knob.width / 2)], [@min, @max], true) 166 | else 167 | return Utils.modulate(value, [0, @_background.width], [@min, @max], true) 168 | else 169 | if @constrained 170 | return Utils.modulate(value, [0 + (@_knob.height / 2), @_background.height - (@_knob.height / 2)], [@min, @max], true) 171 | else 172 | return Utils.modulate(value, [0, @_background.height], [@min, @max], true) 173 | 174 | animateToValue: (value, animationOptions={curve:"spring(300, 25, 0)"}) -> 175 | if @_background.width > @_background.height 176 | animationOptions.properties = {x: @pointForValue(value) - (@_knob.width/2)} 177 | else 178 | animationOptions.properties = {y: @pointForValue(value) - (@_knob.height/2)} 179 | 180 | @_knob.animate(animationOptions) 181 | 182 | # New Constructor 183 | @wrap = (background, fill, knob, options) -> 184 | return wrapSlider(new @(options), background, fill, knob, options) 185 | 186 | onValueChange: (cb) -> @on(Events.SliderValueChange, cb) 187 | 188 | wrapSlider = (instance, background, fill, knob) -> 189 | 190 | if not (background instanceof Layer) 191 | throw new Error("AudioLayer expects a background layer.") 192 | 193 | if not (fill instanceof Layer) 194 | throw new Error("AudioLayer expects a fill layer.") 195 | 196 | if not (knob instanceof Layer) 197 | throw new Error("AudioLayer expects a knob layer.") 198 | 199 | slider = instance 200 | 201 | slider.clip = false 202 | slider.backgroundColor = "transparent" 203 | slider.frame = background.frame 204 | slider.parent = background.parent 205 | slider.index = background.index 206 | 207 | slider.addBackgroundLayer(background) 208 | slider.addFillLayer(fill) 209 | slider.addKnobLayer(knob) 210 | 211 | slider._updateFrame() 212 | slider._updateKnob() 213 | slider._updateFill() 214 | slider._knobDidMove() 215 | 216 | background.onTapStart -> 217 | slider._touchStart(event) 218 | 219 | slider.on "change:frame", -> 220 | slider._updateFrame() 221 | 222 | knob.on "change:size", -> 223 | slider._updateKnob() 224 | 225 | knob.on "change:frame", -> 226 | slider._updateFill() 227 | slider._knobDidMove() 228 | 229 | slider.on "change:max", -> 230 | slider._updateFrame() 231 | slider._updateKnob() 232 | slider._updateFill() 233 | slider._knobDidMove() 234 | 235 | return slider --------------------------------------------------------------------------------