├── .gitignore
├── icon.png
├── module.json
├── snippet.coffee
├── README.md
└── Constraints.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | Demo.framer/*
2 | **/*.sketch
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebcglbailey/framer-constraints/HEAD/icon.png
--------------------------------------------------------------------------------
/module.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "More Constraints",
3 | "description": "Simple and extended constraints for Framer!",
4 | "author": "Sebastian Bailey",
5 | "require": "{Constraints, ConstraintsLayer} = require 'Constraints'",
6 | "install": "Constraints.coffee",
7 | "example": "snippet.coffee",
8 | "thumb": "icon.png"
9 | }
--------------------------------------------------------------------------------
/snippet.coffee:
--------------------------------------------------------------------------------
1 | # UN-COMMENT EACH STEP IN ORDER TO SEE THE PROGRESSION OF THE EXAMPLE
2 | # (UN-COMMENT BY HIGHLIGHTING THE TEXT AND PRESSING CMD + /)
3 |
4 |
5 | # ----------------------------- STEP 1: CONSTRAINTS
6 |
7 | # Try resizing the canvas!
8 |
9 | layer1 = new Layer
10 | backgroundColor: "#fcbb4d"
11 | x: Align.center()
12 |
13 | layer1.constraints =
14 | left: 50, right: 50
15 | top: 50
16 | height: 100
17 | aspectRatioLocked: true
18 |
19 | # USING FUNCTION, - ALL POSSIBLE INPUTS
20 |
21 | # layer1.setConstraints
22 | # left: 20, right: 20
23 | # top: 20, bottom: 20
24 | # widthFactor: 0.5, heightFactor: 0.5
25 | # centerAnchorX: 0.5, centerAnchorY: 0.5
26 | # width: 200, height: 200
27 | # aspectRatioLocked: false
28 | # minWidth: 200, minHeight: 200
29 | # maxWidth: 400, maxHeight: 400
30 |
31 |
32 | # ----------------------------- STEP 2: PINNING
33 |
34 | # layer2 = new Layer
35 | # backgroundColor: "#ccafbd"
36 | # layer2.constraints =
37 | # minHeight: 200
38 | # layer2.pins =
39 | # y:
40 | # layer: layer1
41 | # value: 20
42 | # x:
43 | # layer: layer1
44 | # side: "left"
45 | # width:
46 | # layer: layer1
47 | # height:
48 | # layer: layer1
49 |
50 |
51 |
52 | # USING FUNCTION, - OTHER INPUTS
53 |
54 | # layer2.setPins
55 | # y:
56 | # layer: layer1
57 | # side: "top"
58 | # value: 50
59 | # maxX:
60 | # layer: layer1
61 | # side: "right"
62 | # value: 50
63 | # height:
64 | # layer: layer1
65 | # value: -100
66 |
67 |
68 | # ----------------------------- STEP 3: PUSH PARENT
69 |
70 | # layer3 = new Layer
71 | # backgroundColor: "#aaccbb"
72 | # height: 50
73 | # textLayer = new TextLayer
74 | # parent: layer3
75 | # fontSize: 24
76 | # color: "#444"
77 | # text: "{content}"
78 | # textLayer.template =
79 | # content: "Tap me!"
80 | # textLayer.constraints =
81 | # top: 10
82 | # left: 10
83 | # right: 10
84 | # textLayer.autoSize = true
85 |
86 | # layer3.pins =
87 | # y:
88 | # layer: layer2
89 | # value: 20
90 | # x:
91 | # layer: layer2
92 | # side: "left"
93 | # width: layer: layer2
94 |
95 | # textLayer.pushParent
96 | # direction: "down"
97 | # value: textLayer.constraints.top
98 |
99 | # textString = "This is a demo string that hopefully resizes the parent while it is animating in."
100 | # speed = 0.05
101 |
102 | # layer3.onTap ->
103 | # for i in [0..textString.length]
104 | # do (i) ->
105 | # Utils.delay i*speed, ->
106 | # textLayer.template =
107 | # content: textString.slice 0, i
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # framer-constraints
2 | Simple and Super Constraints for Framer!
3 |
4 | ### Links
5 |
6 | * [Installation](#installation)
7 | * [Constraints](#constraints)
8 | * [Pin](#pin)
9 | * [Push Parent](#push-parent)
10 | * [Layer Extension](#layer-extension)
11 |
12 | ## Installation
13 |
14 | ### Automatic installation with [Framer Modules](https://www.framermodules.com/)
15 |
16 |
17 |
19 |
20 |
21 | ### Manual installation
22 |
23 | Download and move the Constraints.coffee file to your modules folder.
24 |
25 | At the top of your file, place this line of code:
26 |
27 | ```coffeescript
28 | Constraints = require "Constraints"
29 | ```
30 |
31 | Done! That is all the code you need to do to get this module started. Now onto the fun stuff.
32 |
33 |
34 | ## Constraints
35 |
36 | ### setConstraints(options)
37 |
38 | ```coffeescript
39 | layer.setConstraints
40 | left: 20
41 | right: 20
42 | top: 20
43 | aspectRatioLocked: true
44 | maxWidth: 200
45 | ```
46 |
47 | #### Arguments
48 | * `left` – Fixed margin on left side of layer. (Default: `0`)
49 | * `right` – Fixed margin on right side of layer. (Default: `null`)
50 | * `top` – Fixed margin on top side of layer. (Default: `0`)
51 | * `bottom` – Fixed margin on bottom side of layer. (Default: `null`)
52 | * `widthFactor` (0 - 1) – Width ratio of layer:parent. (Default: `null`)
53 | * `heightFactor` (0 - 1) – Height ratio of layer:parent. (Default: `null`)
54 | * `centerAnchorX` (0 - 1) – Ratio of where the layer sits within parent on X-axis. (Default: `null`)
55 | * `centerAnchorY` (0 - 1) – Ratio of where the layer sits within parent on Y-axis. (Default: `null`)
56 | * `aspectRatioLocked` (bool) – Keep the aspect ratio on resize. (Default: `false`)
57 | * `minWidth` – Minimum width for the layer. (Optional)
58 | * `maxWidth` – Maximum width for the layer. (Optional)
59 | * `minHeight` – Minimum height for the layer. (Optional)
60 | * `maxHeight` – Maximum height for the layer. (Optional)
61 |
62 | ### layer.constraints
63 |
64 | ```coffeescript
65 | layer.constraints =
66 | widthFactor: 0.8
67 | centerAnchorX: 0.5
68 | centerAnchorY: 0.1
69 | ```
70 |
71 | You can also set the constraints on a layer like any other property. This must be done after the initialisation of the layer.
72 |
73 |
74 | ## Pin
75 |
76 | ### setPins(options)
77 |
78 | ```coffeescript
79 | layer.setPins
80 | y:
81 | layer: referenceLayer
82 | side: "bottom"
83 | value: 20
84 | width:
85 | layer: referenceLayer
86 | value: -20
87 | ```
88 |
89 | #### Arguments
90 | * `position` (object) – Use the position as the key for each pin reference.
91 | * `x`, `y`, `maxX`, `maxY`
92 | * `size` (object) – You can also pin the `width`, `height` or `size` of a layer to a reference layer.
93 | * Object Arguments
94 | * `layer` – The layer to reference the pin to.
95 | * `side` (string) – The side to pin the layer to. ("top", "bottom", "left", "right")
96 | * Default for `x`: "right"
97 | * Default for `maxX`: "left"
98 | * Default for `y`: "bottom"
99 | * Default for `maxY`: "top"
100 | * `value` (number) – The offset of the pin from the edge. Negative values are allowed for a negative offset.
101 | * Default for position
102 |
103 | ### layer.pins
104 |
105 | ```coffeescript
106 | layer.pins =
107 | x:
108 | layer: referenceLayer
109 | side: "left"
110 | value: -50
111 | y:
112 | layer: referenceLayer
113 | value: 10
114 | height:
115 | layer: referenceLayer
116 | ```
117 |
118 |
119 | ## Push Parent
120 |
121 | ### pushParent(options)
122 |
123 | ```coffeescript
124 | layer.pushParent
125 | direction: "down"
126 | value: 20
127 | ```
128 |
129 | #### Arguments
130 | * `direction` (string) – The direction in which to push the size of the parent. (`"down"`, `"right"`)
131 | * `value` (number) – The margin to add between the layer and the parent. (Default is the original margin upon calling the function).
132 |
133 |
134 | ## Layer Extension
135 |
136 | Although it is not possible to add the properties in your initiation of a `Layer` in Framer, you can use the included `ConstraintLayer` class. This class is exactly the same as `Layer` except you can add `constraints`, `pins` and `pushParent` straight into the initialisation.
137 |
138 | ```coffeescript
139 | layer = new ConstraintLayer
140 | backgroundColor: "#0af"
141 | constraints:
142 | left: 20
143 | right: 20
144 | top: 20
145 | pins:
146 | y:
147 | layer: referenceLayer
148 | value: 20
149 | width:
150 | layer: referenceLayer
151 | pushParent:
152 | direction: "down"
153 | ```
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/Constraints.coffee:
--------------------------------------------------------------------------------
1 | # ---------------------------------------
2 | # CONSTANTS & VARIABLES
3 | # ---------------------------------------
4 |
5 | defaultConstraints =
6 | left: 0
7 | right: null
8 | top: 0
9 | bottom: null
10 | centerAnchorX: null
11 | centerAnchorY: null
12 | widthFactor: null
13 | heightFactor: null
14 | aspectRatioLocked: false
15 | maxHeight: null
16 | minHeight: null
17 | maxWidth: null
18 | minWidth: null
19 |
20 |
21 | # ---------------------------------------
22 | # OBJECT MANIPULATION
23 | # ---------------------------------------
24 |
25 | Object.defineProperty(Layer.prototype, "constraints", {
26 |
27 | get: -> return @_constraints
28 | set: (value) ->
29 | @_constraints = value
30 | @setConstraints value
31 | @emit "change:constraints", value
32 |
33 | });
34 |
35 | Object.defineProperty(Layer.prototype, "pins", {
36 |
37 | get: -> return @_pins
38 | set: (value) ->
39 | @_pins = value
40 | @setPins value
41 | @emit "change:pins", value
42 |
43 | });
44 |
45 |
46 | # ---------------------------------------
47 | # HELPER FUNCTIONS
48 | # ---------------------------------------
49 |
50 | getPropFromSide = (side) ->
51 | switch side
52 | when "left" then return "x"
53 | when "right" then return "maxX"
54 | when "top" then return "y"
55 | when "bottom" then return "maxY"
56 |
57 | getAddOnFromKey = (key) ->
58 | switch key
59 | when "maxX" then return "width"
60 | when "maxY" then return "height"
61 | when "x" then return "width"
62 | when "y" then return "height"
63 |
64 | getKeyFromProp = (prop) ->
65 | switch prop
66 | when "maxY" then return "y"
67 | when "maxX" then return "x"
68 | when "x" then return prop
69 | when "y" then return prop
70 |
71 | getRefFromDirection = (dir) ->
72 | switch dir
73 | when "down"
74 | return {
75 | pos: "y"
76 | extra: "height"
77 | ref: "maxY"
78 | }
79 | when "right"
80 | return {
81 | pos: "x"
82 | extra: "width"
83 | ref: "maxX"
84 | }
85 |
86 | setPinProps = (layer, key, object) ->
87 | if !object.layer?
88 | Utils.throwInStudioOrWarnInProduction "Make sure every pinned property has a layer associated with it."
89 | object.value ?= 0
90 | if key.includes "max"
91 | object.value = -object.value
92 | object.addOn = getAddOnFromKey key
93 | if key.includes "x" then object.side ?= "right"
94 | if key.includes "maxX" then object.side ?= "left"
95 | if key.includes "y" then object.side ?= "bottom"
96 | if key.includes "maxY" then object.side ?= "top"
97 | if object.side is "bottom" or object.side is "right"
98 | object.addOn = getAddOnFromKey key
99 |
100 | refProp = getPropFromSide(object.side) || key
101 | layer[key] = object.layer[refProp] + object.value
102 |
103 | object.layer.onChange key, ->
104 | layer[key] = @[refProp] + object.value
105 | if object.addOn?
106 | object.layer.onChange object.addOn, ->
107 | layer[key] = @[refProp] + object.value
108 |
109 | addMinMax = (layer, values) ->
110 | {minWidth, maxWidth, minHeight, maxHeight} = values
111 |
112 | if minWidth? || maxWidth?
113 | layer.onChange "width", ->
114 | if minWidth? && @width <= minWidth then @width = minWidth
115 | if maxWidth? && @width >= maxWidth then @width = maxWidth
116 | if minHeight? || maxHeight?
117 | layer.onChange "height", ->
118 | if minHeight? && @height <= minHeight then @height = minHeight
119 | if maxHeight? && @height >= maxHeight then @height = maxHeight
120 |
121 |
122 | # ---------------------------------------
123 | # FUNCTIONS
124 | # ---------------------------------------
125 |
126 | Layer::setPins = (pins={}) ->
127 |
128 | for key, object of pins
129 | do (key, object) =>
130 | setPinProps @, key, object
131 |
132 | @layout()
133 |
134 |
135 | Layer::setConstraints = (constraints={}) ->
136 |
137 | layerDefaults = _.assign {}, [
138 | defaultConstraints,
139 | {
140 | left: constraints.left || if constraints.centerAnchorX then null
141 | top: constraints.top || if constraints.centerAnchorY then null
142 | width: @width
143 | height: @height
144 | }
145 | ]
146 |
147 | minMax =
148 | minWidth: constraints.minWidth
149 | maxWidth: constraints.maxWidth
150 | minHeight: constraints.minHeight
151 | maxHeight: constraints.maxHeight
152 | addMinMax @, minMax
153 |
154 | @constraintValues = _.assign layerDefaults, constraints
155 |
156 | @layout()
157 | @states.default = @props
158 |
159 |
160 | Layer::pushParent = (options={}) ->
161 |
162 | if !@parent?
163 | Utils.throwInStudioOrWarnInProduction "Layer must have a parent layer in order to increase it's size."
164 |
165 | ref = getRefFromDirection options.direction
166 | parent = @parent
167 |
168 | options.value ?= parent[ref.extra] - @[ref.ref]
169 | options.direction ?= "down"
170 |
171 | parent[ref.extra] = @[ref.ref] + options.value
172 |
173 | for reference in [ref.pos, ref.extra]
174 | do (reference) =>
175 | @onChange reference, =>
176 | parent[ref.extra] = @[ref.ref] + options.value
177 |
178 |
179 | class exports.ConstraintsLayer extends Layer
180 | constructor: (@options) ->
181 | super @options
182 | @constraints = @options.constraints || null
183 | @pins = @options.pins || null
184 |
185 | if @options.pushParent?
186 | @pushParent @options.pushParent
187 |
--------------------------------------------------------------------------------