├── .gitignore ├── README.md └── material.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An unofficial module for prototyping material design in Framer. 2 | 3 | ## Instructions 4 | 1. Drop `material.coffee` in the `modules` directory of your Framer project. This requires [Framer Studio v1.11.1](http://framerjs.tumblr.com/post/113265323127/introducing-modules-and-more) or higher. 5 | 2. Add `material = require 'material'` to the top of your prototype file. 6 | 7 | ## Examples 8 | ``` 9 | material = require 'material' 10 | 11 | Framer.Defaults.Animation = 12 | curve: material.transformCurve 13 | time: 0.3 14 | 15 | button1 = new material.Button 'Open dialog', backgroundColor: '#e91e63', width: 200 16 | button2 = new material.Button 'This button is auto-sized', fit: true 17 | 18 | card = new material.Card width: 320, height: 200 19 | 20 | progressBar = new material.ProgressBar originY: 0, fillColor: '#ff5722', backgroundColor: '#ffccbc' 21 | progressBar.style.width = '100%' 22 | 23 | # this ripple is based on a tap position 24 | card.on Events.TouchStart, (event) -> 25 | origin = 26 | x: event.offsetX + @screenFrame.x 27 | y: event.offsetY + @screenFrame.y 28 | ripple = new material.Ripple origin: event, container: card, color: '#fff' 29 | Framer.Device.screen.once Events.TouchEnd, () => 30 | ripple.remove() 31 | 32 | # this ripple is centered on the toolbar icon but contained in the toolbar 33 | toolbarIcon.on Events.TouchStart, -> 34 | origin = 35 | x: @screenFrame.x + @width / 2 36 | y: @screenFrame.y + @height / 2 37 | ripple = new material.Ripple origin: origin, container: toolbar, radius: 96 38 | Framer.Device.screen.once Events.TouchEnd, () => 39 | ripple.remove() 40 | ``` 41 | -------------------------------------------------------------------------------- /material.coffee: -------------------------------------------------------------------------------- 1 | exports.transformCurve = 'cubic-bezier(0.4, 0, 0.2, 1)' # fast out, slow in 2 | exports.enterCurve = 'cubic-bezier(0, 0, 0.2, 1)' # linear out, slow in 3 | exports.exitCurve = 'cubic-bezier(0.4, 0, 0.2, 1)' # fast out, linear in 4 | 5 | 6 | exports.Button = class Button extends Layer 7 | constructor: (text, options={}) -> 8 | options.backgroundColor ?= '#03a9f4' 9 | options.color ?= '#fff' 10 | options.shadowY ?= 2 11 | options.shadowBlur ?= 2 12 | options = _.extend options, 13 | borderRadius: 2 14 | height: 36 15 | shadowColor: 'rgba(0, 0, 0, 0.24)' 16 | 17 | super options 18 | 19 | @style.font = '500 14px/36px Roboto' 20 | @style.textTransform = 'uppercase' 21 | @style.textAlign = 'center' 22 | @html = text 23 | 24 | if options.fit 25 | @style.display = 'inline-block' 26 | @style.width = 'auto' 27 | @style.minWidth = '88px' 28 | @style.padding = '0 8px' 29 | 30 | exports.Card = class Card extends Layer 31 | constructor: (options={}) -> 32 | options.backgroundColor ?= '#fff' 33 | options.shadowY ?= 2 34 | options.shadowBlur ?= 2 35 | options = _.extend options, 36 | borderRadius: 2 37 | shadowColor: 'rgba(0, 0, 0, 0.24)' 38 | 39 | super options 40 | 41 | 42 | exports.ProgressBar = class ProgressBar extends Layer 43 | constructor: (options={}) -> 44 | options.backgroundColor ?= 'rgba(0, 0, 0, 0.12)' 45 | options.height ?= 4 46 | options.originY ?= 1 47 | options.scaleY ?= 0 48 | 49 | super options 50 | 51 | @fill = new Layer 52 | superLayer: @ 53 | backgroundColor: options.fillColor || '#03a9f4' 54 | scaleX: 0 55 | originX: 0 56 | @fill.style.width = '100%' 57 | @fill.style.height = '100%' 58 | 59 | animateTo: (fill, time) => 60 | @fill.scaleX = 0 61 | @animate properties: scaleY: 1 62 | fillAnimation = new Animation 63 | layer: @fill 64 | properties: scaleX: fill 65 | time: time || 1 66 | containerAnimation = new Animation 67 | layer: @ 68 | properties: scaleY: 0 69 | fillAnimation.on Events.AnimationEnd, -> containerAnimation.start() 70 | fillAnimation.start() 71 | containerAnimation 72 | 73 | 74 | exports.Ripple = class Ripple extends Layer 75 | constructor: (options={}) -> 76 | options.container ?= Framer.Device.screen 77 | options.origin ?= x: options.container.midX, y: options.container.midY 78 | options.radius ?= Math.max options.container.width, options.container.height 79 | options.color ?= '#000' 80 | 81 | super 82 | backgroundColor: null 83 | width: options.container.width 84 | height: options.container.height 85 | x: options.container.screenFrame.x 86 | y: options.container.screenFrame.y 87 | clip: true 88 | 89 | @placeBefore options.container 90 | 91 | @ink = new Layer 92 | superLayer: @ 93 | backgroundColor: options.color 94 | opacity: 0.25 95 | width: options.radius * 2 96 | height: options.radius * 2 97 | midX: options.origin.x - options.container.screenFrame.x 98 | midY: options.origin.y - options.container.screenFrame.y 99 | borderRadius: '50%' 100 | scale: 0 101 | 102 | @grow() 103 | 104 | 105 | grow: -> 106 | @ink.animate properties: scale: 1 107 | 108 | remove: -> 109 | animation = @ink.animate properties: opacity: 0 110 | animation.on Events.AnimationEnd, => @destroy() 111 | 112 | 113 | # via https://www.facebook.com/groups/framerjs/permalink/580709592056116/ 114 | scaledScreenFrame = -> 115 | frame = Framer.Device.screen.screenFrame 116 | frame.width *= Framer.Device.screen.screenScaleX() 117 | frame.height *= Framer.Device.screen.screenScaleY() 118 | 119 | frame.x += (Framer.Device.screen.width - frame.width) * Framer.Device.screen.originX 120 | frame.y += (Framer.Device.screen.height - frame.height) * Framer.Device.screen.originY 121 | 122 | return frame 123 | 124 | exports.eventToOrigin = (event) -> 125 | x: (event.x - scaledScreenFrame().x) / Framer.Device.screen.screenScaleX() 126 | y: (event.y - scaledScreenFrame().y) / Framer.Device.screen.screenScaleY() 127 | --------------------------------------------------------------------------------