├── README.md
└── src
└── slider.coffee
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 | Framer Slider
4 |
5 |
6 |
7 |
8 |
9 |
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
--------------------------------------------------------------------------------