├── .babelrc ├── logo.png ├── index.js ├── package.json ├── LICENSE ├── .gitignore ├── README.md └── dist.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthkp/react-vanilla-tilt/HEAD/logo.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import VanillaTilt from 'vanilla-tilt' 3 | 4 | class Tilt extends React.Component { 5 | constructor(props) { 6 | super(props) 7 | this.tiltRef = React.createRef() 8 | } 9 | 10 | componentDidMount() { 11 | VanillaTilt.init(this.tiltRef.current, this.props.options) 12 | } 13 | 14 | render() { 15 | return
16 | } 17 | } 18 | 19 | export default Tilt 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vanilla-tilt", 3 | "version": "1.0.0", 4 | "description": "React wrapper around vanilla-tilt", 5 | "main": "dist.js", 6 | "scripts": { 7 | "build": "babel index.js -o dist.js" 8 | }, 9 | "files": ["dist.js"], 10 | "keywords": ["react", "vanilla", "tilt"], 11 | "author": "siddharthkp", 12 | "license": "MIT", 13 | "dependencies": { 14 | "vanilla-tilt": "1.4.1" 15 | }, 16 | "devDependencies": { 17 | "babel-cli": "6.26.0", 18 | "babel-preset-env": "1.6.1", 19 | "babel-preset-react": "6.24.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Siddharth Kshetrapal 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. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | A smooth 3D tilt React component wrapped around vanilla-tilt.js 5 |

6 |

7 | 8 |   9 | 10 | #### install 11 | 12 | ``` 13 | npm install react-vanilla-tilt 14 | ``` 15 | 16 |   17 | 18 | #### usage 19 | 20 | ```jsx 21 | import React from 'react' 22 | import Tilt from 'react-vanilla-tilt' 23 | 24 | 25 |
26 | You can put whatever you want inside this 27 |
28 |
29 | ``` 30 | 31 |   32 | 33 | #### options 34 | 35 | ```js 36 | { 37 | reverse: false, // reverse the tilt direction 38 | max: 35, // max tilt rotation (degrees) 39 | perspective: 1000, // Transform perspective, the lower the more extreme the tilt gets. 40 | scale: 1, // 2 = 200%, 1.5 = 150%, etc.. 41 | speed: 300, // Speed of the enter/exit transition 42 | transition: true, // Set a transition on enter/exit. 43 | axis: null, // What axis should be disabled. Can be X or Y. 44 | reset: true, // If the tilt effect has to be reset on exit. 45 | easing: "cubic-bezier(.03,.98,.52,.99)", // Easing on enter/exit. 46 | glare: false, // if it should have a "glare" effect 47 | "max-glare": 1, // the maximum "glare" opacity (1 = 100%, 0.5 = 50%) 48 | "glare-prerender": false // false = VanillaTilt creates the glare elements for you, otherwise 49 | // you need to add .js-tilt-glare>.js-tilt-glare-inner by yourself 50 | } 51 | ``` 52 | 53 | ```jsx 54 | import React from 'react' 55 | import Title from 'react-vanilla-tilt' 56 | 57 | 58 |
59 | You can put whatever you want inside this 60 |
61 |
62 | ``` 63 | 64 |   65 | 66 | #### Related projects 67 | 68 | - [card-vibes](https://github.com/siddharthkp/card-vibes): The card component with good vibes built on top of this project 69 | 70 | Thanks to 71 | - [vanilla-tilt.js](https://github.com/micku7zu/vanilla-tilt.js) by [@micku7zu](https://github.com/micku7zu) 72 | - [tilt.js](https://github.com/gijsroge/tilt.js) by [@gijsroge](https://github.com/gijsroge) 73 | 74 |   75 | 76 | #### like it? 77 | 78 | :star: this repo 79 | 80 |   81 | 82 | #### license 83 | 84 | MIT © [siddharthkp](https://github.com/siddharthkp) 85 | -------------------------------------------------------------------------------- /dist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _react = require('react'); 12 | 13 | var _react2 = _interopRequireDefault(_react); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 18 | 19 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 20 | 21 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 22 | 23 | function VanillaTilt(element) { 24 | this.width = null; 25 | this.height = null; 26 | this.left = null; 27 | this.top = null; 28 | this.transitionTimeout = null; 29 | this.updateCall = null; 30 | 31 | this.updateBind = this.update.bind(this); 32 | this.resetBind = this.reset.bind(this); 33 | 34 | this.element = element; 35 | 36 | this.settings = this.getSettings(); 37 | 38 | this.element.style.boxShadow = this.getShadow(this.settings); 39 | 40 | this.reverse = this.settings.reverse ? -1 : 1; 41 | 42 | this.addEventListeners(); 43 | } 44 | 45 | VanillaTilt.prototype.getShadow = function getShadow(settings, values) { 46 | if (!values) values = { percentageX: 50, percentageY: 50 }; 47 | var shadow = this.settings.shadow; 48 | 49 | return shadow.color + ' ' + (shadow.x.min + 0.01 * values.percentageX * (shadow.x.max - shadow.x.min)) + 'px ' + (shadow.y.min + 0.01 * values.percentageY * (shadow.y.max - shadow.y.min)) + 'px ' + shadow.spread + 'px'; 50 | }; 51 | 52 | VanillaTilt.prototype.isSettingTrue = function isSettingTrue(setting) { 53 | return setting === '' || setting === true || setting === 1; 54 | }; 55 | 56 | VanillaTilt.prototype.addEventListeners = function addEventListeners() { 57 | this.onMouseEnterBind = this.onMouseEnter.bind(this); 58 | this.onMouseMoveBind = this.onMouseMove.bind(this); 59 | this.onMouseLeaveBind = this.onMouseLeave.bind(this); 60 | 61 | this.element.addEventListener('mouseenter', this.onMouseEnterBind); 62 | this.element.addEventListener('mousemove', this.onMouseMoveBind); 63 | this.element.addEventListener('mouseleave', this.onMouseLeaveBind); 64 | }; 65 | 66 | VanillaTilt.prototype.removeEventListeners = function removeEventListeners() { 67 | this.element.removeEventListener('mouseenter', this.onMouseEnterBind); 68 | this.element.removeEventListener('mousemove', this.onMouseMoveBind); 69 | this.element.removeEventListener('mouseleave', this.onMouseLeaveBind); 70 | }; 71 | 72 | VanillaTilt.prototype.destroy = function destroy() { 73 | clearTimeout(this.transitionTimeout); 74 | if (this.updateCall !== null) cancelAnimationFrame(this.updateCall); 75 | 76 | this.reset(); 77 | 78 | this.removeEventListeners(); 79 | this.element.vanillaTilt = null; 80 | delete this.element.vanillaTilt; 81 | 82 | this.element = null; 83 | }; 84 | 85 | VanillaTilt.prototype.onMouseEnter = function onMouseEnter(event) { 86 | this.updateElementPosition(); 87 | this.element.style.willChange = 'transform'; 88 | this.setTransition(); 89 | }; 90 | 91 | VanillaTilt.prototype.onMouseMove = function onMouseMove(event) { 92 | if (this.updateCall !== null) { 93 | cancelAnimationFrame(this.updateCall); 94 | } 95 | 96 | this.event = event; 97 | this.updateCall = requestAnimationFrame(this.updateBind); 98 | }; 99 | 100 | VanillaTilt.prototype.onMouseLeave = function onMouseLeave(event) { 101 | this.setTransition(); 102 | requestAnimationFrame(this.resetBind); 103 | }; 104 | 105 | VanillaTilt.prototype.reset = function reset() { 106 | this.event = { 107 | pageX: this.left + this.width / 2, 108 | pageY: this.top + this.height / 2 109 | }; 110 | 111 | this.element.style.transform = 'perspective(' + this.settings.perspective + 'px) ' + 'rotateX(0deg) ' + 'rotateY(0deg) ' + 'scale3d(1, 1, 1)'; 112 | 113 | this.element.style.boxShadow = this.getShadow(this.settings); 114 | }; 115 | 116 | VanillaTilt.prototype.getValues = function getValues() { 117 | var x = (this.event.clientX - this.left) / this.width; 118 | var y = (this.event.clientY - this.top) / this.height; 119 | 120 | x = Math.min(Math.max(x, 0), 1); 121 | y = Math.min(Math.max(y, 0), 1); 122 | 123 | var tiltX = (this.reverse * (this.settings.max / 2 - x * this.settings.max)).toFixed(2); 124 | var tiltY = (this.reverse * (y * this.settings.max - this.settings.max / 2)).toFixed(2); 125 | var angle = Math.atan2(this.event.clientX - (this.left + this.width / 2), -(this.event.clientY - (this.top + this.height / 2))) * (180 / Math.PI); 126 | 127 | return { 128 | tiltX: tiltX, 129 | tiltY: tiltY, 130 | percentageX: x * 100, 131 | percentageY: y * 100, 132 | angle: angle 133 | }; 134 | }; 135 | 136 | VanillaTilt.prototype.updateElementPosition = function updateElementPosition() { 137 | var rect = this.element.getBoundingClientRect(); 138 | 139 | this.width = this.element.offsetWidth; 140 | this.height = this.element.offsetHeight; 141 | this.left = rect.left; 142 | this.top = rect.top; 143 | }; 144 | 145 | VanillaTilt.prototype.update = function update() { 146 | var values = this.getValues(); 147 | 148 | this.element.style.transform = 'perspective(' + this.settings.perspective + 'px) ' + 'rotateX(' + (this.settings.axis === 'x' ? 0 : values.tiltY) + 'deg) ' + 'rotateY(' + (this.settings.axis === 'y' ? 0 : values.tiltX) + 'deg) ' + 'scale3d(' + this.settings.scale + ', ' + this.settings.scale + ', ' + this.settings.scale + ')'; 149 | 150 | this.element.style.boxShadow = this.getShadow(this.settings, values); 151 | 152 | this.element.dispatchEvent(new CustomEvent('tiltChange', { detail: values })); 153 | 154 | this.updateCall = null; 155 | }; 156 | 157 | VanillaTilt.prototype.setTransition = function setTransition() { 158 | var _this = this; 159 | 160 | clearTimeout(this.transitionTimeout); 161 | this.element.style.transition = this.settings.speed + 'ms ' + this.settings.easing; 162 | 163 | this.transitionTimeout = setTimeout(function () { 164 | _this.element.style.transition = ''; 165 | }, this.settings.speed); 166 | }; 167 | 168 | VanillaTilt.prototype.getSettings = function getSettings() { 169 | var defaultSettings = { 170 | reverse: true, 171 | max: 15, 172 | perspective: 1000, 173 | easing: 'cubic-bezier(.03,.98,.52,.99)', 174 | scale: '1', 175 | speed: '300', 176 | transition: true, 177 | axis: null, 178 | reset: true, 179 | shadow: { 180 | color: 'rgba(20, 26, 40, 0.2)', 181 | x: { min: -5, max: 5 }, 182 | y: { min: 3.5, max: 10.5 }, 183 | spread: 42 184 | } 185 | }; 186 | 187 | return defaultSettings; 188 | }; 189 | 190 | VanillaTilt.init = function init(elements, settings) { 191 | if (elements instanceof Node) { 192 | elements = [elements]; 193 | } 194 | 195 | if (elements instanceof NodeList) { 196 | elements = [].slice.call(elements); 197 | } 198 | 199 | if (!(elements instanceof Array)) { 200 | return; 201 | } 202 | 203 | elements.forEach(function (element) { 204 | if (!('vanillaTilt' in element)) { 205 | element.vanillaTilt = new VanillaTilt(element, settings); 206 | } 207 | }); 208 | }; 209 | 210 | var cardStyles = { 211 | width: '300px', 212 | padding: '30px', 213 | margin: '10px', 214 | background: '#fff', 215 | borderRadius: '4px', 216 | color: '#364962', 217 | fontSize: '16px', 218 | lineHeight: 1.6 219 | }; 220 | 221 | var Card = function (_React$Component) { 222 | _inherits(Card, _React$Component); 223 | 224 | function Card() { 225 | _classCallCheck(this, Card); 226 | 227 | return _possibleConstructorReturn(this, (Card.__proto__ || Object.getPrototypeOf(Card)).apply(this, arguments)); 228 | } 229 | 230 | _createClass(Card, [{ 231 | key: 'componentDidMount', 232 | value: function componentDidMount() { 233 | VanillaTilt.init(document.querySelectorAll('[data-tilt]')); 234 | } 235 | }, { 236 | key: 'render', 237 | value: function render() { 238 | var styles = Object.assign({}, cardStyles, this.props.style); 239 | return _react2.default.createElement('div', _extends({ 'data-tilt': true, style: styles }, this.props)); 240 | } 241 | }]); 242 | 243 | return Card; 244 | }(_react2.default.Component); 245 | 246 | exports.default = Card; 247 | --------------------------------------------------------------------------------