├── .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 |