├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── next.config.js ├── package.json ├── pages ├── components │ └── ScrollPanel.js ├── index.js ├── sections │ ├── AnimationDuration.js │ ├── Banner.js │ ├── DefaultConfigs.js │ ├── FromTo.js │ ├── Links.js │ ├── Loops.js │ ├── Target.js │ ├── Throttle.js │ ├── What.js │ └── index.js └── styles.js ├── rollup.config.js └── src ├── Component.js ├── Function.WithHooks.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | dist 4 | .next 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .next 3 | pages 4 | out 5 | src 6 | *.config.js 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 giladk 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-scroll-rotate ([demo](https://giladk.github.io/react-scroll-rotate/)) 2 | Very simple react component for rotate element based on scroll position 3 | 4 | [![NPM](https://img.shields.io/npm/v/react-scroll-rotate.svg)](https://www.npmjs.com/package/react-scroll-rotate) 5 | 6 | ## Install 7 | 8 | ```bash 9 | npm install --save react-scroll-rotate 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```jsx 15 | import React, { Component } from 'react'; 16 | import { ScrollRotate } from 'react-scroll-rotate'; 17 | 18 | function MyTitleComp(props) { 19 | return ( 20 | Hello world 21 | ) 22 | } 23 | ``` 24 | 25 | ## Props 26 | 27 | | Property | Type | Default | Description | 28 | |:--------------|:-------------------|:--------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------| 29 | | `target` | string | null | Target element id. By default it will use window scroll | 30 | | `throttle` | number | null | Use 'throttle' prop to invoke the update once in X seconds | 31 | | `from` | number | 0 | Rotation start point | 32 | | `to` | number | 360 | Rotation end point | 33 | | `method` | string | 'px' | Use 'perc' to change rotation based on scroll percantges. | 34 | | `loops` | number | 1 | When using method 'perc', define how many loops component will do while scrolling to bottom | 35 | | `animationDuration` | number | 0.1 | Animation duration | 36 | 37 | ## License 38 | 39 | MIT 40 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exportPathMap: function() { 3 | return { 4 | '/': { page: '/' } 5 | }; 6 | } 7 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-scroll-rotate", 3 | "version": "0.0.7", 4 | "description": "Very simple react component for rotate element based on scroll position", 5 | "main": "dist/bundle.js", 6 | "module": "dist/es.module.js", 7 | "jsnext:main": "dist/es.module.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "start": "next", 11 | "build": "rollup -c", 12 | "build-pages": "next build", 13 | "export-pages": "next export" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/giladk/react-scroll-rotate" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "peerDependencies": { 23 | "prop-types": "^15.7.2", 24 | "react": "^16.8.6", 25 | "react-dom": "^16.8.6" 26 | }, 27 | "devDependencies": { 28 | "@fortawesome/fontawesome-svg-core": "^1.2.19", 29 | "@fortawesome/free-brands-svg-icons": "^5.9.0", 30 | "@fortawesome/free-regular-svg-icons": "^5.9.0", 31 | "@fortawesome/free-solid-svg-icons": "^5.9.0", 32 | "@fortawesome/react-fontawesome": "^0.1.4", 33 | "babel-plugin-external-helpers": "^6.22.0", 34 | "lodash": "^4.17.11", 35 | "next": "^8.1.0", 36 | "rollup-plugin-babel": "^4.3.3", 37 | "rollup-plugin-commonjs": "^10.0.1", 38 | "rollup-plugin-node-resolve": "^5.2.0", 39 | "rollup-plugin-peer-deps-external": "^2.2.0", 40 | "styled-components": "^4.3.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pages/components/ScrollPanel.js: -------------------------------------------------------------------------------- 1 | function ScrollPanel(props) { 2 | return
3 |
4 |

Scroll me


5 |

Scroll me


6 |

Scroll me


7 |

Scroll me


8 |

Scroll me


9 |

Scroll me


10 |

Scroll me


11 |

Scroll me

12 |
13 |
14 | } 15 | 16 | export default ScrollPanel; -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {PageContainer } from './styles'; 4 | import { Banner, What, DefaultConfigs, Loops, Target, FromTo, Throttle, AnimationDuration } from "./sections/index"; 5 | 6 | function Home() { 7 | return 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | } 18 | 19 | export default Home; -------------------------------------------------------------------------------- /pages/sections/AnimationDuration.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { useState } from "react"; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faHourglass } from '@fortawesome/free-regular-svg-icons' 5 | import { faArrowUp, faMoon } from '@fortawesome/free-solid-svg-icons' 6 | import { CenterContainer, ExampleSection, ExampleTitle, 7 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, 8 | ExampleCodePre, ExampleDemoWrapper, ExampleDemoButtonWrapper 9 | } from '../styles'; 10 | import ScrollPanel from '../components/ScrollPanel'; 11 | 12 | 13 | export function AnimationDuration(props) { 14 | let [loops,setLoops] = useState(1); 15 | let [duration,setDuration] = useState(0.1); 16 | return ( 17 | 18 | 19 | 20 | Animation duration 21 | 22 | 23 | 24 |

Use 'animationDuration' props to set animation duration by Seconds
Defaults: 0.1

25 |
26 | Loops: 27 | {setLoops(parseFloat(e.target.value))}} /> 29 | Duration: 30 | {setDuration(parseFloat(e.target.value))}} /> 32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n\n`+ 40 | ` 0.3s Animation duration` 42 | } 43 | 44 | 45 |
46 | 47 | 48 | By px:
49 | {(duration).toFixed(2)} d 50 | 51 | 52 | 53 | By Loops:
54 | 0 d 55 | 56 | 57 | 58 | {(duration).toFixed(2)} d 59 | 60 | 61 | 62 | {(duration+0.2).toFixed(2)} d 63 | 64 | 65 | 66 | {(duration+0.4).toFixed(2)} d 67 | 68 | 69 | 70 | 71 |
72 |
73 |
74 | 75 | 76 |
77 | )} -------------------------------------------------------------------------------- /pages/sections/Banner.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faCoffee, faMagic,faKiwiBird} from '@fortawesome/free-solid-svg-icons' 4 | import { BannerWrapper, CenterContainer, Title, NpmInstallWrapper, BannerIconPositionOne, 5 | BannerIconOne, BannerIconPositionTwo, BannerIconPositionThree } from '../styles'; 6 | 7 | export function Banner(props) { 8 | return ( 9 | 10 | 11 | react-scroll-rotate 12 | npm install --save react-scroll-rotate 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | )} 38 | 39 | // default Banner; -------------------------------------------------------------------------------- /pages/sections/DefaultConfigs.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faCog, faCat } from '@fortawesome/free-solid-svg-icons' 4 | import { CenterContainer, ExampleSection, ExampleTitle, 5 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, ExampleCodePre, ExampleDemoWrapper, 6 | } from '../styles'; 7 | 8 | 9 | export function DefaultConfigs(props) { 10 | return ( 11 | 12 | 13 | 14 | Default configs 15 | 16 | 17 | 18 |

By default, without any prop, ScrollRotate will rotate based on Scroll top 1px=1deg

19 | 20 | 21 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n\n Rotate me\n`} 22 | 23 | 24 |
25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 | )} -------------------------------------------------------------------------------- /pages/sections/FromTo.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { useState } from "react"; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faArrowUp } from '@fortawesome/free-solid-svg-icons' 5 | import { CenterContainer, ExampleSection, ExampleTitle, 6 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, 7 | ExampleCodePre, ExampleDemoWrapper, ExampleDemoButtonWrapper 8 | } from '../styles'; 9 | import ScrollPanel from '../components/ScrollPanel'; 10 | 11 | 12 | export function FromTo(props) { 13 | let [loops,setLoops] = useState(1); 14 | let [from,setFrom] = useState(90); 15 | let [to,setTo] = useState(270); 16 | return ( 17 | 18 | 19 | 20 | From - To 21 | 22 | 23 | 24 |

Use 'from' and 'To' props to adjust the range of the circle
Defaults: 0 - 360

25 |
26 | Loops: 27 | {setLoops(parseFloat(e.target.value))}} /> 29 | From: 30 | {setFrom(parseInt(e.target.value))}} /> 32 | To: 33 | {setTo(parseInt(e.target.value))}} /> 35 | 36 |
37 | 38 |
39 | 40 | 41 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n\n`+ 42 | ` From 90 to 270 Deg` 44 | } 45 | 46 | 47 |
48 | 49 | 50 | By px 51 | 52 | 53 | 54 | {loops} loop 55 | 56 | 57 | 58 | {loops + 1} loops 59 | 60 | 61 | 62 | {loops + 2} loops 63 | 64 | 65 | 66 | 67 | 68 |
69 |
70 | 71 | 72 |
73 | )} -------------------------------------------------------------------------------- /pages/sections/Links.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faCoffee, faMagic,faKiwiBird} from '@fortawesome/free-solid-svg-icons' 4 | import { faGithub } from '@fortawesome/free-brands-svg-icons' 5 | import { LinksWrapper, CenterContainer, LinkWrapper, LinkText } from '../styles'; 6 | 7 | export function Links(props) { 8 | return ( 9 | 10 | 11 | 12 | 13 | src 14 | 15 | 16 | 17 | react-scroll-rotate 18 | 19 | 20 | 21 | )} 22 | 23 | // default Banner; -------------------------------------------------------------------------------- /pages/sections/Loops.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faRedo } from '@fortawesome/free-solid-svg-icons' 4 | import { CenterContainer, ExampleSection, ExampleTitle, 5 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, 6 | ExampleCodePre, ExampleDemoWrapper, ExampleDemoButtonWrapper, ExampleDemoButton 7 | } from '../styles'; 8 | 9 | 10 | export function Loops(props) { 11 | return ( 12 | 13 | 14 | 15 | Loops 16 | 17 | 18 | 19 |

Use 'method' prop with 'perc' value to rotate based of scroll percantges

20 |

21 | Use 'loops' prop to define how many rotations ScrollRotateonent will do during the scroll
22 | Defualt is 1 23 |

24 | 25 | 26 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n\n`+ 27 | ` One loop\n`+ 29 | `\n`+ 30 | ` Three loops\n`+ 31 | `` 32 | } 33 | 34 | 35 |
36 | 37 | 38 | 1 39 | 40 | 41 | 2 42 | 43 | 44 | 3 45 | 46 | 47 | 4 48 | 49 | 50 | 5 51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 | )} -------------------------------------------------------------------------------- /pages/sections/Target.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { useState } from "react"; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faHashtag, faHippo } from '@fortawesome/free-solid-svg-icons' 5 | import { CenterContainer, ExampleSection, ExampleTitle, 6 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, 7 | ExampleCodePre, ExampleDemoWrapper 8 | } from '../styles'; 9 | import ScrollPanel from '../components/ScrollPanel'; 10 | 11 | 12 | export function Target(props) { 13 | let [loops,setLoops] = useState(1); 14 | return ( 15 | 16 | 17 | 18 | Target 19 | 20 | 21 | 22 |

Select target to control the ScrollRotateonent.

23 |
24 | Loops: 25 | {setLoops(parseInt(e.target.value))}} /> 27 |
28 | 29 |
30 | 31 | 32 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n\n`+ 33 | ` I will response to #target-element-id scroll\n`+ 34 | `` 35 | } 36 | 37 | 38 |
39 | 40 | {loops} loop 41 | 42 | 43 | 44 | {loops+1} loops 45 | 46 | 47 | 48 | {loops+2} loops 49 | 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 | )} -------------------------------------------------------------------------------- /pages/sections/Throttle.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { useState } from "react"; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faClock } from '@fortawesome/free-regular-svg-icons' 5 | import { faDragon } from '@fortawesome/free-solid-svg-icons' 6 | import { CenterContainer, ExampleSection, ExampleTitle, 7 | AnimationDurationNote, 8 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, 9 | ExampleCodePre, ExampleDemoWrapper, ExampleDemoButtonWrapper 10 | } from '../styles'; 11 | import ScrollPanel from '../components/ScrollPanel'; 12 | 13 | 14 | export function Throttle(props) { 15 | let [loops,setLoops] = useState(1); 16 | let [throttle,setThrottle] = useState(0.1); 17 | return ( 18 | 19 | 20 | 21 | Throttle 22 | 23 | 24 | 25 |

Use 'throttle' prop to invoke the update once in X seconds
26 | Defaults: null
27 | *Throttle is not used by default
28 | *When you use 'throttle' prop you probably want to also add 'animationDuration' prop with +- same value 29 |

30 |
31 | Throttle: 32 | {setThrottle(parseFloat(e.target.value))}} /> 34 | Loops: 35 | {setLoops(parseFloat(e.target.value))}} /> 37 |
38 | 39 |
40 | 41 | 42 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n\n`+ 43 | ` I will rotate every 0.1s` 45 | } 46 | 47 | 48 |
49 | 50 | * = With animation duration
51 | By px:
52 | {(throttle).toFixed(2)} th' 53 | 54 | 55 | 56 | 57 | 58 | * 59 | 60 | Loops:
61 | {(throttle).toFixed(2)} th' 62 | 63 | 64 | 65 | 66 | 67 | * 68 | 69 | {(throttle + 0.4).toFixed(2)} th' 70 | 71 | 72 | 73 | 74 | 75 | * 76 | 77 | 78 |
79 |
80 |
81 | 82 | 83 |
84 | )} -------------------------------------------------------------------------------- /pages/sections/What.js: -------------------------------------------------------------------------------- 1 | import { ScrollRotate } from '../../src'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faStar } from '@fortawesome/free-solid-svg-icons' 4 | import { faGithub , faNpm } from '@fortawesome/free-brands-svg-icons' 5 | import { CenterContainer, ExampleSection, WhatTitle, 6 | ExampleContentAndDemo, ExampleContent, ExampleCodeWrapper, 7 | ExampleCodePre, ExampleDemoWrapper, LinksWrapper, LinkWrapper, LinkText 8 | } from '../styles'; 9 | 10 | 11 | export function What(props) { 12 | return ( 13 | 14 | 15 | 16 |
17 | What ? 18 |
19 |
20 | 21 | 22 | 23 | src 24 | 25 | 26 | 27 | react-scroll-rotate 28 | 29 | 30 |
31 |
32 | 33 | 34 |

react-scroll-rotate is a very simple react ScrollRotateonent that rotate children based on scroll poistion.

35 |

So for example, this "What?" title will look like this:

36 | 37 | 38 | {`import { ScrollRotate } from 'react-scroll-rotate';\n\n // Styled ScrollRotateonent for example\n What <ScrollRotate>?</ScrollRotate>\n`} 39 | 40 | 41 |
42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 | )} -------------------------------------------------------------------------------- /pages/sections/index.js: -------------------------------------------------------------------------------- 1 | export {Banner} from './Banner'; 2 | export {What} from './What'; 3 | export {DefaultConfigs} from './DefaultConfigs'; 4 | export {Loops} from './Loops'; 5 | export {Target} from './Target'; 6 | export {FromTo} from './FromTo'; 7 | export {Throttle} from './Throttle'; 8 | export {AnimationDuration} from './AnimationDuration'; 9 | export {Links} from './Links'; -------------------------------------------------------------------------------- /pages/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { EDEADLK } from 'constants'; 3 | 4 | export const PageContainer = styled.div``; 5 | 6 | export const ExampleSection = styled.div` 7 | width:100%; 8 | display:flex; 9 | flex-direction:column; 10 | position:relative; 11 | padding-bottom:32px; 12 | `; 13 | 14 | 15 | export const BannerWrapper = styled.div` 16 | background-color: #3e9fffa6; 17 | height: 50vh; 18 | box-shadow: 2px 1px 8px 0px #5f5f5f; 19 | position:relative; 20 | `; 21 | 22 | 23 | export const Title = styled.h1` 24 | padding-top:8vh; 25 | padding-bottom:2vh; 26 | font-size: 60px; 27 | @media (max-width: 768px) { 28 | font-size: 40px; 29 | } 30 | @media (max-width: 500px) { 31 | font-size: 30px; 32 | } 33 | text-align: center; 34 | color: #ffffff; 35 | 36 | `; 37 | 38 | 39 | export const CenterContainer = styled.div` 40 | flex:1; 41 | width: 100%; 42 | max-width:704px; 43 | margin:0 auto; 44 | display:flex; 45 | flex:1; 46 | flex-direction:${props=>props.flex==='col' ? 'column' : 'row'} 47 | align-items:start; 48 | @media (max-width: 768px) { 49 | margin:0 32px; 50 | width: auto; 51 | } 52 | `; 53 | 54 | export const NpmInstallWrapper = styled.div` 55 | background: #484848; 56 | padding: 8px 24px; 57 | border-radius: 5px; 58 | font-size: 1em; 59 | color: #fff; 60 | `; 61 | 62 | export const BannerIconPositionOne = styled.div` 63 | position:absolute; 64 | bottom:-25px; 65 | left:37%; 66 | @media (max-width: 768px) { 67 | left:16%; 68 | top:40vh; 69 | } 70 | `; 71 | 72 | export const BannerIconPositionTwo = styled.div` 73 | position:absolute; 74 | top:73%; 75 | left:65%; 76 | @media (max-width: 768px) { 77 | top:73%; 78 | left:75%; 79 | } 80 | `; 81 | 82 | export const BannerIconPositionThree = styled.div` 83 | position:absolute; 84 | top:16%; 85 | left:72%; 86 | @media (max-width: 768px) { 87 | top:5%; 88 | left:60%; 89 | } 90 | `; 91 | 92 | 93 | export const BannerIconOne = styled.div` 94 | width:40px; 95 | height:40px; 96 | border-radius:10px; 97 | border:5px solid; 98 | background-color:#f7f7f7; 99 | color:green; 100 | display:flex; 101 | align-items:center; 102 | justify-content: center; 103 | box-shadow: 4px 4px; 104 | ` 105 | 106 | export const LinksWrapper = styled.div` 107 | padding:8px 0px; 108 | font-size:16px; 109 | display:flex; 110 | @media (max-width: 768px) { 111 | flex-direction:column; 112 | } 113 | `; 114 | export const LinkWrapper = styled.a` 115 | color:#000; 116 | border:1px solid; 117 | border-radius:5px; 118 | padding: 8px; 119 | flex-direction: row; 120 | display: flex; 121 | align-items: center; 122 | margin-left:16px; 123 | text-decoration: none; 124 | `; 125 | 126 | export const LinkText = styled.div` 127 | margin-left:8px; 128 | `; 129 | 130 | export const WhatTitle = styled.div` 131 | font-size:32px; 132 | border-bottom:1px solid gray; 133 | width:100%; 134 | padding-top:40px; 135 | display:flex; 136 | justify-content:space-between; 137 | align-items: flex-end; 138 | `; 139 | 140 | 141 | export const ExampleTitle = styled.div` 142 | font-size:32px; 143 | border-bottom:1px solid gray; 144 | width:100%; 145 | padding-top:40px; 146 | `; 147 | 148 | 149 | export const ExampleContent = styled.div` 150 | font-size:20px; 151 | width:100%; 152 | padding-top:12px; 153 | p{ 154 | margin:0px; 155 | padding:16px 0px; 156 | } 157 | `; 158 | 159 | export const ExampleDemoWrapper = styled.div` 160 | // position:absolute; 161 | // top:50%; 162 | // margin-top:-25px; 163 | // height:50px; 164 | // right:10%; 165 | // width:50px; 166 | padding-left:32px;display:flex; 167 | flex-direction:column; 168 | justify-content:center; 169 | ` 170 | 171 | export const ExampleContentAndDemo = styled.div` 172 | display:flex; 173 | flex-direction:row; 174 | align-items:center; 175 | width:100%; 176 | `; 177 | export const ExampleCodeWrapper = styled.div` 178 | background-color: #000000; 179 | color: #c5c5c5; 180 | flex:1; 181 | padding:16px; 182 | `; 183 | 184 | export const ExampleCodePre = styled.pre` 185 | white-space: pre-wrap; 186 | font-size:14px; 187 | margin: 0px; 188 | `; 189 | export const ExampleDemoButtonWrapper = styled.div` 190 | margin:12px 0; 191 | position:relative; 192 | `; 193 | 194 | export const AnimationDurationNote = styled.div` 195 | poisition:absoulute; 196 | bottom:0; 197 | left:0; 198 | color:#36a1ff; 199 | `; 200 | 201 | export const ExampleDemoButton = styled.div` 202 | width:30px; 203 | height:30px; 204 | border-radius:10px; 205 | border:5px solid green; 206 | // border-color:#15ca15 #ff5e00 #a200a2 #55c6f5; 207 | box-shadow:2px 2px 1px 0px #273e28a3; 208 | display:flex; 209 | align-items:center; 210 | justify-content:center; 211 | `; -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import babel from 'rollup-plugin-babel' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | 5 | module.exports = { 6 | input: 'src/index.js', 7 | output: [ 8 | { 9 | file: 'dist/bundle.js', 10 | format: 'cjs', 11 | sourcemap: true 12 | }, 13 | { 14 | file: 'dist/es.module.js', 15 | format: 'es', 16 | sourcemap: true 17 | }, 18 | ], 19 | plugins: [ 20 | babel({ 21 | exclude: "node_modules/**", 22 | "presets": [ 23 | ["@babel/preset-env", {"modules": false}], 24 | ["@babel/preset-react", {"modules": false}], 25 | ] 26 | }), 27 | resolve(), 28 | commonjs({ 29 | include: 'node_modules/**' 30 | }) 31 | ], 32 | external: ['react', 'prop-types'] 33 | }; -------------------------------------------------------------------------------- /src/Component.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import throttle from "lodash/throttle"; 3 | import PropTypes from 'prop-types'; 4 | 5 | const wrapperStyles = { 6 | display: 'inline-block', 7 | position: 'relative' 8 | } 9 | 10 | export class ScrollRotate extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | clientHeight:0, 15 | scrollTop:0, 16 | topPercentage:0, 17 | scrollOnce:false, 18 | getOnce:false 19 | } 20 | this.handleFunc = null; 21 | } 22 | 23 | getStartValues() { 24 | const { target } = this.props; 25 | if (target){ 26 | // document.getElementById(props.target) 27 | // TODO 28 | }else { 29 | const clientHeight = window.innerHeight; 30 | const scrollTop = window.scrollY; 31 | const topPercentage = scrollTop / ( document.documentElement.offsetHeight - clientHeight ) * 100; 32 | this.setState({clientHeight,scrollTop,topPercentage}); 33 | } 34 | this.setState({getOnce:true}); 35 | } 36 | 37 | updateValues(e) { 38 | const { target } = this.props; 39 | const se = target ? e.srcElement : e.srcElement.scrollingElement; 40 | const { clientHeight , scrollTop } = se; 41 | const topPercentage = scrollTop / ( (target ? se.scrollHeight : se.offsetHeight) - clientHeight ) * 100; 42 | this.setState({clientHeight,scrollTop,topPercentage,scrollOnce:true}); 43 | } 44 | 45 | setScrollHandle(){ 46 | let handleScrollThrottle = throttle((e) => { 47 | this.updateValues(e); 48 | }, this.props.throttle ? 1000 * this.props.throttle : 0, {leading:false}); 49 | 50 | let handleScroll = (e) => { 51 | this.updateValues(e); 52 | }; 53 | 54 | const { target } = this.props; 55 | let handle = this.props.throttle ? handleScrollThrottle : handleScroll; 56 | let targetElement = target ? document.getElementById(target) : window; 57 | if (targetElement !== null){ 58 | targetElement.addEventListener('scroll', handle); 59 | return () => { 60 | targetElement.removeEventListener('scroll', handle); 61 | }; 62 | } 63 | return () => {}; 64 | } 65 | 66 | getAnimationStyles() { 67 | const {scrollOnce} = this.state; 68 | const {animationDuration} = this.props; 69 | return { 70 | transform: `rotate(${this.getDeg()}deg)`, 71 | transition: !scrollOnce ? '' : `transform ${(animationDuration || animationDuration===0) ? animationDuration : '0.1'}s`, 72 | } 73 | } 74 | 75 | getDeg() { 76 | const { method } = this.props; 77 | const from = this.props.from || 0; 78 | const to = this.props.to || 360; 79 | if (method === 'perc'){ 80 | const { topPercentage : perc} = this.state; 81 | const loops = this.props.loops || 1; 82 | const currentLoop = parseInt( loops*perc/100)+ ( perc !== 100 ? 1 : Number.isInteger(loops) ? 0 : 1); 83 | return ((perc / 100) - (1/loops*(currentLoop-1))) * (to-from) * loops + from + (360*(currentLoop-1)*(from>to?-1:1)); 84 | } else { 85 | const { scrollTop } = this.state; 86 | const currentLoop = parseInt(scrollTop/(to-from)); 87 | return (( scrollTop - (currentLoop*(to-from)) ) *(from>to?-1:1) + (from + (360*currentLoop))); 88 | } 89 | } 90 | 91 | componentDidMount() { 92 | this.getStartValues(); 93 | this.removeHandleFunc = this.setScrollHandle(); 94 | } 95 | 96 | componentDidUpdate(prevProps) { 97 | if (prevProps.throttle !== this.props.throttle){ 98 | this.removeHandleFunc(); 99 | this.removeHandleFunc = this.setScrollHandle(); 100 | } 101 | } 102 | 103 | componentWillUnmount() { 104 | this.removeHandleFunc(); 105 | } 106 | 107 | 108 | render(){ 109 | const {getOnce} = this.state; 110 | const rotateStyles = this.getAnimationStyles(); 111 | return
112 | {getOnce &&
113 | {this.props.children} 114 |
115 | } 116 |
117 | } 118 | } 119 | 120 | ScrollRotate.propTypes = { 121 | target: PropTypes.string, 122 | throttle: PropTypes.number, 123 | from: PropTypes.number, 124 | to: PropTypes.number, 125 | method: PropTypes.string, 126 | loops: PropTypes.number, 127 | animationDuration: PropTypes.number 128 | } 129 | 130 | ScrollRotate.defaultProps = { 131 | target: null, 132 | throttle: null, 133 | from: 0, 134 | to: 360, 135 | method: 'px', 136 | loops: 1, 137 | animationDuration :0.1 138 | } 139 | -------------------------------------------------------------------------------- /src/Function.WithHooks.js: -------------------------------------------------------------------------------- 1 | import React ,{ useState, useEffect } from "react"; 2 | import throttle from "lodash/throttle"; 3 | import PropTypes from 'prop-types'; 4 | 5 | export function ScrollRotate(props) { 6 | 7 | let [values, setValues] = useState({clientHeight:0,scrollTop:0,topPercentage:0,scrollOnce:false}); 8 | let [getOnce, setGetOnce] = useState(false); 9 | 10 | let updateValues = (e) => { 11 | const se = props.target ? e.srcElement : e.srcElement.scrollingElement; 12 | const { clientHeight , scrollTop } = se; 13 | const topPercentage = scrollTop / ( (props.target ? se.scrollHeight : se.offsetHeight) - clientHeight ) * 100; 14 | setValues({clientHeight,scrollTop,topPercentage,scrollOnce:true}); 15 | } 16 | 17 | let getStartValues = () => { 18 | if (props.target){ 19 | // document.getElementById(props.target) 20 | // TODO 21 | }else { 22 | const clientHeight = window.innerHeight; 23 | const scrollTop = window.scrollY; 24 | const topPercentage = scrollTop / ( document.documentElement.offsetHeight - clientHeight ) * 100; 25 | setValues({clientHeight,scrollTop,topPercentage}); 26 | } 27 | setGetOnce(true); 28 | } 29 | 30 | useEffect(() => { 31 | let handleScrollThrottle = throttle((e) => { 32 | updateValues(e); 33 | }, props.throttle, {leading:false}); 34 | 35 | let handleScroll = (e) => { 36 | updateValues(e); 37 | }; 38 | 39 | let handle = props.throttle ? handleScrollThrottle : handleScroll; 40 | let targetElement = props.target ? document.getElementById(props.target) : window; 41 | targetElement.addEventListener('scroll', handle); 42 | getStartValues(); 43 | return () => { 44 | targetElement.removeEventListener('scroll', handle); 45 | } 46 | 47 | },[props.throttle]); 48 | 49 | const wrapperStyles = { 50 | display: 'inline-block', 51 | position: 'relative' 52 | } 53 | 54 | const getDeg = () => { 55 | const from = props.from || 0; 56 | const to = props.to || 360; 57 | if (props.method === 'perc'){ 58 | const { topPercentage : perc} = values; 59 | const loops = props.loops || 1; 60 | const currentLoop = parseInt(loops*perc/100)+ ( perc !== 100 ? 1 : Number.isInteger(loops) ? 0 : 1); 61 | return ((perc / 100) - (1/loops*(currentLoop-1))) * (to-from) * loops + from + (360*(currentLoop-1)*(from>to?-1:1)); 62 | } else { 63 | const { scrollTop } = values; 64 | const currentLoop = parseInt(scrollTop/(to-from)); 65 | return ( scrollTop - (currentLoop*(to-from)) ) * (from>to?-1:1) + from + (360*currentLoop); 66 | } 67 | } 68 | 69 | const getAnimationStyles = () => { 70 | return { 71 | transform: `rotate(${getDeg()}deg)`, 72 | transition: !values.scrollOnce ? '' : `transform ${props.animationDuration ? props.animationDuration : '0.1'}s`, 73 | } 74 | } 75 | 76 | // const getLoop = (perc) => { 77 | // const loops = props.loops || 1; 78 | // if (props.method === 'perc'){ 79 | // return parseInt(loops*perc/100)+ ( perc !== 100 ? 1 : 0); 80 | // } else { 81 | // return parseInt(values.scrollTop/(props.to-props.from)); 82 | // } 83 | // } 84 | 85 | return
86 | {getOnce &&
87 | {props.children} 88 |
89 | } 90 | {/* {props.debuge && Info: {`H : ${values.clientHeight} , ST : ${values.scrollTop} , % : ${ values.topPercentage } , deg: ${getDeg()}, Loop: ${getLoop(values.topPercentage)}` }} */} 91 |
92 | } 93 | 94 | ScrollRotate.propTypes = { 95 | target: PropTypes.string, 96 | throttle: PropTypes.number, 97 | from: PropTypes.number, 98 | to: PropTypes.number, 99 | method: PropTypes.string, 100 | loops: PropTypes.number, 101 | animationDuration: PropTypes.number 102 | } 103 | 104 | ScrollRotate.defaultProps = { 105 | target: null, 106 | throttle: null, 107 | from: 0, 108 | to: 360, 109 | method: 'px', 110 | loops: 1, 111 | animationDuration :0.1 112 | } 113 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { ScrollRotate } from './Component'; --------------------------------------------------------------------------------