├── .gitattributes ├── LICENSE ├── ParallaxComponents.coffee ├── README.md ├── example.coffee ├── module.json └── parallaxComponents.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marc Krenn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ParallaxComponents.coffee: -------------------------------------------------------------------------------- 1 | 2 | defaultParallaxOriginZ = 200 3 | 4 | setupParallax = (component) -> 5 | for segment in component.content.children 6 | segment._initPoint = segment.point 7 | 8 | unless segment._hasListeners? 9 | # Add listeners to recalculate parallax when size or position was modified 10 | segment.onChange "size", -> @_initPoint = @point 11 | segment.onChange "z", -> applyParallax(component) 12 | 13 | for axis in ["x", "y"] 14 | do (axis) -> 15 | segment.onChange axis, -> 16 | @_initPoint[axis] = @point[axis] unless @_parallaxUpdate 17 | 18 | segment._hasListeners = true 19 | 20 | for descendant in segment.descendants 21 | 22 | # Try to guess the right parallaxOrigin{x,y} for ParallaxScrollComponent 23 | for axis in ["x", "y"] 24 | component._parallaxOrigin[axis] = segment[axis] if component._parallaxOrigin[axis] is null 25 | 26 | descendant._segment = segment 27 | descendant._initPoint = descendant.point 28 | 29 | unless descendant._hasListeners? 30 | # Add listeners to recalculate parallax when size or position was modified 31 | descendant.onChange "size", -> 32 | @_initPoint = @point 33 | for axis in ["x", "y"] 34 | updateLayerParallax(this, axis, component.content[axis], component._parallaxOrigin) 35 | 36 | descendant.onChange "z", -> applyParallax(component) 37 | for axis in ["x", "y"] 38 | do (axis) -> 39 | descendant.onChange axis, -> 40 | @_initPoint[axis] = @point[axis] unless @_parallaxUpdate 41 | updateLayerParallax(this, axis, component.content[axis], component._parallaxOrigin) 42 | 43 | descendant._hasListeners = true 44 | 45 | # Ugly workaround: Wait until next tik, so all children/descendants are guaranteed to be ready 46 | Utils.delay 0, -> applyParallax(component) 47 | 48 | 49 | # Apply / update parallax of single layer 50 | updateLayerParallax = (layer, axis, offset, origin) -> 51 | layer._parallaxUpdate = true 52 | try layer[axis] = (offset + layer._segment._initPoint[axis] - origin[axis]) / origin.z * layer.z + layer._initPoint[axis] 53 | layer._parallaxUpdate = false 54 | 55 | 56 | # Apply / update parallax of all layers 57 | applyParallax = (component, axes = ["x", "y"], offset = 0) -> 58 | for axis in axes 59 | for segment in component.content.children 60 | if segment.children.length is 0 61 | segment._parallaxUpdate = true 62 | try segment[axis] = offset / component._parallaxOrigin.z * segment.z + segment._initPoint[axis] 63 | segment._parallaxUpdate = false 64 | else 65 | for descendant in segment.descendants 66 | updateLayerParallax(descendant, axis, offset, component._parallaxOrigin) 67 | 68 | 69 | class exports.ParallaxScrollComponent extends ScrollComponent 70 | 71 | @define "parallaxOrigin", 72 | default: {x: null, y: null, z: defaultParallaxOriginZ}, 73 | get: -> @_parallaxOrigin 74 | set: (val) -> 75 | @_parallaxOrigin = {} 76 | for key of val 77 | @_parallaxOrigin[key] = val[key] 78 | 79 | @_parallaxOrigin.x ?= null 80 | @_parallaxOrigin.y ?= null 81 | @_parallaxOrigin.z ?= defaultParallaxOriginZ 82 | 83 | 84 | constructor: -> 85 | super 86 | 87 | @content.onChange "children", => Utils.delay 0, => setupParallax(this) 88 | 89 | for axis in ["x", "y", "z"] 90 | do (axis) => @content.onChange axis, => applyParallax(this, axis, @content[axis]) 91 | 92 | 93 | class exports.ParallaxPageComponent extends PageComponent 94 | 95 | @define "parallaxOrigin", 96 | default: {x: null, y: null, z: defaultParallaxOriginZ}, 97 | get: -> @_parallaxOrigin 98 | set: (val) -> 99 | @_parallaxOrigin = {} 100 | for key of val 101 | @_parallaxOrigin[key] = val[key] 102 | 103 | @_parallaxOrigin.x ?= null 104 | @_parallaxOrigin.y ?= null 105 | @_parallaxOrigin.z ?= defaultParallaxOriginZ 106 | 107 | 108 | constructor: -> 109 | super 110 | 111 | @content.onChange "children", => Utils.delay 0, => setupParallax(this) 112 | 113 | for axis in ["x", "y", "z"] 114 | do (axis) => @content.onChange axis, => applyParallax(this, axis, @content[axis]) 115 | 116 | 117 | #TODO: add ParallaxFlowComponent 118 | 119 | 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | # framer-ParallaxComponents 5 | 6 | Extends Framer's *Page*- and *ScrollComponents* with a consistent parallax scrolling effect. 7 |
8 |
9 | Read the article on [Medium](https://blog.framer.com/parallax-6292c10d2be). 10 |
11 | 12 | 13 | Install with Framer Modules 15 | 16 | ## Demo Projects 17 | 18 | [parallaxScrollComponent](https://framer.cloud/bCUnQ) 19 | 20 | [parallaxPageComponent](https://framer.cloud/XIJJj) 21 | 22 | [parallaxStaticLabel](https://framer.cloud/rySfn/) 23 | 24 | ## Contact & Help 25 | If you need further assistance or want to leave me some feedback, you can reach me via [Twitter](https://twitter.com/marc_krenn), [Facebook](https://www.facebook.com/groups/framerjs/permalink/1187824361344633/) or [Slack](https://framer-slack-signup.herokuapp.com/) or here on Github. 26 | -------------------------------------------------------------------------------- /example.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | page = new ParallaxPageComponent 4 | frame: Screen.frame 5 | parallaxOrigin: z: 200 # defines overall strength of parallax (relative to 'z'-values) 6 | backgroundColor: "white" 7 | scrollVertical: false 8 | 9 | 10 | # Add pages 11 | for i in [0...10] 12 | 13 | pageContainer = new Layer 14 | size: page.size 15 | backgroundColor: "white" 16 | style: border: "1px inset" 17 | 18 | # Set 'z'-property to add parallax: 19 | 20 | imageMask = new Layer 21 | parent: pageContainer 22 | point: Align.center 23 | size: 325 24 | clip: true 25 | borderRadius: 10 26 | z: 50 # enables parallax effect for this layer 27 | 28 | image = new Layer 29 | parent: imageMask 30 | point: Align.center 31 | size: 450 32 | image: Utils.randomImage() # doesnt always load, so we'll add 33 | backgroundColor: Utils.randomColor().darken(20) # as a fallback 34 | z: -50 # parallax effect relative to its parent 35 | 36 | counter = new TextLayer 37 | parent: image 38 | point: Align.center() 39 | text: i + 1 40 | textAlign: "center" 41 | fontSize: 100 42 | fontWeight: 200 43 | color: "white" 44 | height: 130 45 | z: 250 46 | 47 | label = new TextLayer 48 | text: "Photo" 49 | fontSize: 38 50 | fontWeight: 800 51 | color: "black" 52 | parent: pageContainer 53 | z: 25 54 | y: imageMask.y - 70 55 | x: imageMask.x + 20 56 | 57 | page.addPage(pageContainer, "right") 58 | 59 | 60 | -------------------------------------------------------------------------------- /module.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ParallaxComponents", 3 | "description": "Extends Framer's Page- and ScrollComponents with a consistent parallax scrolling effect.", 4 | "author": "Marc Krenn", 5 | 6 | "require": "{ParallaxPageComponent, ParallaxScrollComponent} = require 'ParallaxComponents'", 7 | "install": "ParallaxComponents.coffee", 8 | "example": "example.coffee", 9 | 10 | "thumb": "parallaxComponents.png" 11 | } -------------------------------------------------------------------------------- /parallaxComponents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marckrenn/framer-ParallaxComponents/95be89a25ffd0050647b478416adbe859761496d/parallaxComponents.png --------------------------------------------------------------------------------