ScrollController is a Framer module that helps you to animate, pin and trigger events based on scroll position. It's the module you need, if you want to:
- animate based on scroll position
- start an animation at a specific scroll position
- synchronise an animation to the scroll movement
- pin a layer indefinitely at a specific scroll position
- pin a layer for a limited amount of scroll progress
- add callbacks at specific scroll positions passing a progress parameter
- easily create parallax effect
- create infinite scrolling
- create sticky headers
Works with Framer v120 or later
Installation
Demo
Quick start
Documentation
Known limitations
Releases
License
Author
Acknowledgement
- Open your project in Framer.
- Start Framer Modules and select
ADD MODULE. - Search for
ScrollController, once found hit enter to install into your prototype.
- Open your project in Framer.
- Download and unzip the module.
- Drag the file
ScrollController.coffeeinto the code editor. - Change
ScrollControllerto{ScrollController}in the require command
To get started using the ScrollController you must start by creating a Controller to make the required connection to the ScrollComponent. Secondly you must add at least one Scene which defines in what part of the page/view something should happen. Finally you may add at least one Tween or Pin to define what needs to happen. Alternatively you may listen for events with callbacks to trigger your prototype.
# Import module
{ScrollController} =require'ScrollController'# Create ScrollComponentmyScrollComponent=newScrollComponentframe:Screen.framescrollHorizontal:false# Create containermyContainer=newLayerparent:myScrollComponent.contentwidth:Screen.widthheight:Screen.height*2backgroundColor:null# Create layermyLayer=newLayerparent: myContainer
point:Align.center# Create controllermyController=newScrollControllersource: myScrollComponent
# Add scenemyScene=myController.addScenetriggerLayer: myLayer
duration:200guide:true# Add tweeenmyTween=myScene.addTweento: {rotation:-90, scale:0.2, backgroundColor:"#00AAFF"}
options: {time:0.25, curve:"Spring"}
# Add pinmyPin=myScene.addPin()# Listen for event with callbackmyScene.onProgress (event, instance) ->myTextLayer.text="#{event.progress}"ScrollController consists of 4 components: Controller, Scene, Tween and Pin. Learn about each components and its properties in the documentation:
The controller establishes the required connection to the ScrollComponent and manages attached scenes by either updating or refreshing them whenever the ScrollComponent changes its size or when its scroll position changes. The controller manages one direction - create two controllers if you need to manage both horizontal and vertical movement at the same time.
| Property | Type | Default | Description |
|---|---|---|---|
new ScrollController | Class | - | Create controller |
Controller.source | Object | - | Get controller source |
Controller.name | String | "." | Get/Set controller name |
Controller.direction | String | "vertical" | Get controller scroll direction |
Controller.throttle | Number | 0 | Get/Set controller throttling |
Controller.enabled | Boolean | true | Get/Set controller enabled state |
Controller.addScene | Method | - | Add scene to controller |
Controller.removeScene | Method | - | Remove scene from controller |
Controller.scrollTo | Method | - | Scroll to scene, progress, layer or offset |
Controller.update | Method | - | Force controller update |
Controller.refresh | Method | - | Force controller refresh |
Controller.remove | Method | - | Remove controller |
Create controller.
source– ScrollComponent instance – requiredname– controller namedirection– controller scroll directionthrottle– controller throttlingenabled– controller enabled state
# Create controller with default propertiesmyController=newScrollControllersource: myScrollComponent
# Create controller with custom propertiesmyController=newScrollControllersource: myScrollComponent
name:"myControllerName"direction:"horizontal"throttle:0.01enabled:falseAdd scene to controller.
name– scene nametriggerLayer– scene triggerLayer to define the start of the sceneoffset– scene offset, from top/left of screen or from triggerLayerduration– scene durationneedle– scene needle position, from begining to end of screen (0 to 1)reverse– scene reversable stateenabled– scene enabled stateguide– scene guide markersguideNeedleColor– scene guide needle colorguideStartColor– scene guide start colorguideEndColor– scene guide end colorguideIndent– scene guide indent
# Add scene to controller with default propertiesmyScene=myController.addScene()
# Add scene to controller with custom propertiesmyScene=myController.addScenename:"mySceneName"triggerLayer: myLayer
offset:-100duration:200needle:0.65reverse:falseenabled:falseguide:trueguideNeedleColor:"blue"guideStartColor:"green"guideEndColor:"red"guideIndent:40Remove scene from controller.
reset– reset scene and all connected tweens and pins
# Remove scenes and all connected tweens and pinsmyController.removeScene myScene
# Remove and reset scene and all connected tweens and pinsmyController.removeScene myScene, trueScroll to scene, scene progress, layer and offset
element– scene or layerprogress– scene progress (0 to 1)offset– offset
# Scroll to scenemyController.scrollToelement: myScene
# Scroll to scene progressmyController.scrollToelement: myScene
progress:0.25# Scroll to layermyController.scrollToelement: myLayer
# Scroll to offsetmyController.scrollTooffset:100# Scroll to scene progress with offsetmyController.scrollToelement: myScene
progress:0.25offset:100Update controller by recalculating scene, tween and pin properties then automatically refreshing the controller. This happens automatically and should only be used when required.
# Update controllermyController.update()Refresh controller by redrawing scenes, tweens and pins. This happens automatically and should only be used when required.
# Refresh controllermyController.refresh()Remove controller.
reset– reset controller and all connected scenes, tweens and pins
# Remove controller and all connected scenes, tweens and pinsmyController.remove()
# Remove and reset controller and all connected scenes, tweens and pinsmyController.removetrueThe scene defines in what part of the page/view something should happen and manages all attached tweens and pins. The scene has a start position defined by either a layer (triggerLayer) and/or an offset (from the triggerLayer or from top/left of screen if no triggerLayer is defined). The scene can also have an end if a duration is defined. Every scene has a needle (defined by a number between 0 and 1, start of screen is 0, end of screen is 1). The needle triggers events once it reaches the start, the end and whenever it enters, progresses and leaves the scene. A scene can be setup easily and precisely by enabling the guide.
| Property | Type | Default | Description |
|---|---|---|---|
Scene.source | Object | ScrollComponent | Get scene ScrollComponent |
Scene.controller | Object | Controller | Get scene controller |
Scene.name | String | "." | Get/Set scene name |
Scene.triggerLayer | Layer | Undefined | Get/Set triggerLayer |
Scene.needle | Number | 0.5 | Get/Set scene needle (0 to 1) |
Scene.offset | Number | 0 | Get/Set scene offset |
Scene.duration | Number | 0 | Get/Set scene duration |
Scene.reverse | Boolean | true | Get/Set scene reversable state |
Scene.enabled | Boolean | true | Get/Set scene enabled state |
Scene.guide | Boolean | false | Get scene guide |
Scene.guideNeedleColor | Color | "#0066FF" | Get scene guide needle guide |
Scene.guideStartColor | Color | "#009933" | Get scene guide start color |
Scene.guideEndColor | Color | "#FF3333" | Get scene guide end color |
Scene.state | String | "before", "during" or "after" | Get scene state |
Scene.progress | Number | - | Get scene progress (0 to 1) |
Scene.start | Number | - | Get scene start |
Scene.end | Number | - | Get scene end |
Scene.needlePos | Number | - | Get scene needle position |
Scene.scrollOffset | Number | - | Get scene scroll offset |
Scene.offsetStart | Number | - | Get scene offset start |
Scene.offsetEnd | Number | - | Get scene offset end |
Scene.tweens | Array | - | Get scene tweens |
Scene.pins | Array | - | Get scene pins |
Scene.addTween | Method | - | Add scene tween |
Scene.removeTween | Method | - | Remove scene tween |
Scene.addPin | Method | - | Add scene pin |
Scene.removePin | Method | - | Remove scene pin |
Scene.trigger | Method | - | Trigger scene event |
Scene.update | Method | - | Force scene update |
Scene.refresh | Method | - | Force scene refresh |
Scene.remove | Method | - | Remove scene |
Scene.onChangeValue | Event | - | On change value |
Scene.onShift | Event | - | On shift |
Scene.onUpdate | Event | - | On update |
Scene.onEnter | Event | - | On enter |
Scene.onStart | Event | - | On start |
Scene.onProgress | Event | - | On progress |
Scene.onEnd | Event | - | On end |
Scene.onLeave | Event | - | On leave |
Add tween to scene.
name– tween namelayer– tween layerfrom– tween from properties object - required if no to propertiesto– tween to properties object - required if no from propertiesoptions– tween animation objectinit– tween initialisation stateenabled– tween enabled state
# Add tween to scene with default propertiesmyTween=myScene.addTweenfrom: {opacity:0}
# Add tween to scene with custom propertiesmyTween=myScene.addTweenname:"myTween"layer: myOtherLayer
from: {rotation:0, scale:2, backgroundColor:"white"}
to: {rotation:-90, scale:0.2, backgroundColor:"#00AAFF"}
options: {time:0.25, curve:"Spring", colorModel:"rgb"}
init:trueenabled:falseRemove tween from scene.
reset– reset tween
# Remove tweenmyScene.removeTween myTween
# Remove and reset tweenmyScene.removeTween myTween, trueAdd pin to scene.
name– pin namelayer– pin layerenabled– pin enabled state
# Add pin to scene with default propertiesmyPin=myScene.addPin()
# Add pin to scene with custom propertiesmyPin=myScene.addPinname:"myPinName"layer: myOtherLayer
enabled:falseRemove pin from scene.
reset– reset pin
# Remove pinmyScene.removePin myPin
# Remove and reset pinmyScene.removePin myPin, trueTrigger scene event.
name– event name to trigger – requiredevent– event object for callback
# Trigger scene eventmyScene.triggername:"enter"event: {myVar}Update by recalculating scene, tween and pin properties then automatically refreshing the scene. This happens automatically and should only be used when required.
# Update scenemyScene.update()Refresh scene by redrawing tweens and pins. This happens automatically and should only be used when required.
# Refresh scenemyScene.refresh()Remove scene from controller.
reset– reset scene and all connected tweens and pins
# Remove scene and all connected tweens and pinsmyScene.remove()
# Remove and reset scene and all connected tweens and pinsmyScene.removetrueListen for scene events.
# Listen for scene changemyScene.onChangeValue->print"Scene changed"# Listen for scene shiftmyScene.onShift->print"Scene shifted"# Listen for scene updatemyScene.onUpdate->print"Scene updated"# Listen for scene entermyScene.onEnter->print"Scene entered"# Listen for scene startmyScene.onStart->print"Scene started"# Listen for scene progressmyScene.onProgress->print"Scene progressed"# Listen for scene endmyScene.onEnd->print"Scene ended"# Listen for scene leavemyScene.onLeave->print"Scene left"# Listen for scene change with callbackmyScene.onChangeValue (event, instance) ->print"Scene changed"print"Event: #{event.property}, #{event.value}"# Listen for scene shift with callbackmyScene.onShift (event, instance) ->print"Scene shifted"print"Event: #{event.reason}"# Listen for scene update with callbackmyScene.onUpdate (event, instance) ->print"Scene updated"print"Event: #{event.start}, #{event.end}, #{event.scrollPos}"# Listen for scene enter with callbackmyScene.onEnter (event, instance) ->print"Scene entered"print"Event: #{event.progress}, #{event.state}, #{event.scrollDir}"# Listen for scene start with callbackmyScene.onStart (event, instance) ->print"Scene started"print"Event: #{event.progress}, #{event.state}, #{event.scrollDir}"# Listen for scene progress with callbackmyScene.onProgress (event, instance) ->print"Scene progressed"print"Event: #{event.progress}, #{event.state}, #{event.scrollDir}"# Listen for scene end with callbackmyScene.onEnd (event, instance) ->print"Scene ended"print"Event: #{event.progress}, #{event.state}, #{event.scrollDir}"# Listen for scene leave with callbackmyScene.onLeave (event, instance) ->print"Scene left"print"Event: #{event.progress}, #{event.state}, #{event.scrollDir}"The tween defines what happens in a scene by animating a defined layer (triggerLayer by default) between states defined by from and/or to properties. The tween will start its animation (with its defined animation options) when its scene starts or it will modulate based on scroll progress between from and to when its scene starts until it ends if the scene has a duration. Multiple tweens can be added on the same layer with the same properties if their scenes are defined in succession and the tweens define both from and to properties and disable their initialisation.
| Property | Type | Default | Description |
|---|---|---|---|
Tween.source | Object | ScrollComponent | Get tween ScrollComponent |
Tween.controller | Object | Controller | Get tween controller |
Tween.scene | Object | Scene | Get tween scene |
Tween.name | String | "." | Get/Set tween name |
Tween.layer | Object | Scene.triggerLayer | Get tween layer |
Tween.from | Object | - | Get tween from properties |
Tween.to | Object | - | Get tween to properties |
Tween.options | Object | Framer.Defaults.Animation | Get tween animation options |
Tween.init | Boolean | true | Get tween animation initialisation state |
Tween.enabled | Boolean | true | Get/Set tween enabled state |
Tween.remove | Method | - | Remove tween |
Remove tween from scene.
reset– reset tween
# Remove tweenmyTween.remove()
# Remove and reset tweenmyTween.removetrueThe pin defines what happens in a scene by pinning a defined layer (triggerLayer by default). The pin will pin its layer when its scene starts and unpins its layer when its scene ends if the scene has a duration. Multiple pins can be added on the same layer in the same direction if their scenes are defined in succession.
| Property | Type | Default | Description |
|---|---|---|---|
Pin.source | Object | ScrollComponent | Get pin ScrollComponent |
Pin.controller | Object | Controller | Get pin controller |
Pin.scene | Object | Scene | Get pin scene |
Pin.name | String | "." | Get/Set pin name |
Pin.layer | Layer | Scene.triggerLayer | Get pin layer |
Pin.enabled | Boolean | true | Get/Set pin enabled state |
Pin.state | String | "pinned" or "unpinned" | Get pin state |
Pin.remove | Method | - | Remove pin |
Remove pin from scene.
reset– reset pin
# Remove pinmyPin.remove()
# Remove and reset pinmyPin.removetrue- It is not recommended to use ScrollController for “clicky” mousewheel scrolling due to the nature of its scrolling behavior.
- Overlapping pins on the same layer in the same direction is not supported, use successive pinning instead.
- Overlapping tweens on the same layer with the same properties is not supported, use successive tweening instead and define
from,toandinit: falsefor all but the first tween. - Removing tweens on a layer with remaining tweens is not supported.
- v1.0.1 - Minor optimisations
- v1.0.0 - Initial release
This project is licensed under the MIT license.
Developed by Jesper Bentzen.
- Website: jesperbentzen.com
- Twitter: @jbentzen
Star this repository if you like it, and if you find that this plugin somehow saves your day, then consider buying me a coffee via PayPal. It will surely help motivate me to further support this module. :)
A special thank you to @janpaepke for creating ScrollMagic which helped guide the approach used in this module. Also thank you to the entire Framer Slack community and especially @steveruizok and @marckrenn for sharing their thoughts and hard work to study and build upon.

