├── .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 | [](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 ? \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';
--------------------------------------------------------------------------------