├── .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 | Install with Framer Modules 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 | --------------------------------------------------------------------------------