├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── exmaple
└── anchor-point-test
│ ├── .expo-shared
│ └── assets.json
│ ├── .gitignore
│ ├── AnchorPointDemo.tsx
│ ├── App.tsx
│ ├── SmallTestCard.tsx
│ ├── TestAnimatedCard.tsx
│ ├── TestCard.tsx
│ ├── app.json
│ ├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
│ ├── babel.config.js
│ ├── package.json
│ └── tsconfig.json
├── index.ts
├── package.json
└── res
├── anchor_point.png
├── external_point.png
├── rotate.gif
├── rotateXY.gif
└── rotateZ.gif
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "es6": true
4 | },
5 | "extends": [
6 | "eslint:recommended",
7 | "plugin:react/recommended",
8 | "plugin:@typescript-eslint/eslint-recommended"
9 | ],
10 | "globals": {
11 | "Atomics": "readonly",
12 | "SharedArrayBuffer": "readonly"
13 | },
14 | "parser": "@typescript-eslint/parser",
15 | "parserOptions": {
16 | "ecmaFeatures": {
17 | "jsx": true
18 | },
19 | "ecmaVersion": 11,
20 | "sourceType": "module"
21 | },
22 | "plugins": [
23 | "react",
24 | "@typescript-eslint"
25 | ],
26 | "rules": {
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
117 |
118 | package-lock.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 RY Zheng
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-native-anchor-point
2 |
3 |
4 | Provide a simple, tricky but powerful function, `withAnchorPoint` , like Anchor Point in iOS, Pivot Point in Android, `transform-origin` in css to achieve better 3D transform animation in React-Native.
5 |
6 | > Make the 3D transform easier in React Native
7 |
8 | 
9 | 
10 | 
11 |
12 | ## Getting Started
13 |
14 | install the `react-native-anchor-point`
15 |
16 | ```
17 | yarn add react-native-anchor-point
18 | ```
19 | or
20 | ```
21 | npm install react-native-anchor-point
22 | ```
23 |
24 | ## Example
25 |
26 | ```javascript
27 | import { withAnchorPoint } from 'react-native-anchor-point';
28 |
29 | getTransform = () => {
30 | let transform = {
31 | transform: [{ perspective: 400 }, { rotateX: rotateValue }],
32 | };
33 | return withAnchorPoint(transform, { x: 0.5, y: 1.5 }, { width: CARD_WIDTH, height: CARD_HEIGHT });
34 | };
35 |
36 |
37 | ```
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/AnchorPointDemo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, Text, View, ScrollView, Button, Animated } from 'react-native';
3 | import TestCard from './TestCard';
4 | import SmallTestCard from './SmallTestCard';
5 | import TestAnimatedCard from './TestAnimatedCard';
6 |
7 | const ExternalAnchorPointViews = () => {
8 | let cards = []
9 | const initialDegree = -45;
10 | for(let i = 0; i < 2; i++) {
11 | const deg = i * 90 + initialDegree;
12 | cards.push(
13 |
14 | )
15 | }
16 |
17 | return cards;
18 | }
19 |
20 | export default class AnchorPointDemo extends React.Component {
21 | render() {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {ExternalAnchorPointViews()}
36 |
37 |
38 |
39 |
43 |
47 |
48 |
49 |
50 |
54 |
58 |
62 |
63 |
64 |
69 |
74 |
79 |
80 |
81 |
82 |
86 |
90 |
94 |
95 |
96 |
100 |
104 |
108 |
109 |
110 |
111 |
115 |
119 |
123 |
124 |
125 |
129 |
133 |
137 |
138 |
139 |
143 |
147 |
151 |
152 |
153 | )
154 | }
155 | }
156 |
157 | const styles = StyleSheet.create({
158 | container: {
159 | flex: 1,
160 | backgroundColor: '#fff',
161 | marginTop: 64
162 | },
163 | row: {
164 | flexDirection: 'row',
165 | paddingHorizontal: 60,
166 | justifyContent: 'space-between'
167 | },
168 | marginTop: {
169 | marginTop: 300
170 | },
171 | zoomIn: {
172 | transform: [{ scale: 0.5 }]
173 | }
174 | });
175 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/App.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from 'expo-status-bar';
2 | import React from 'react';
3 | import { StyleSheet, Text, View } from 'react-native';
4 | import AnchorPointDemo from './AnchorPointDemo';
5 |
6 | export default function App() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | const styles = StyleSheet.create({
15 | container: {
16 | flex: 1,
17 | backgroundColor: '#fff',
18 | alignItems: 'center',
19 | justifyContent: 'center',
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/SmallTestCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | StyleSheet,
4 | View,
5 | Animated,
6 | ViewProps,
7 | Text,
8 | RotateXTransform,
9 | RotateYTransform,
10 | PerpectiveTransform,
11 | RotateTransform,
12 | RotateZTransform,
13 | ScaleTransform,
14 | ScaleXTransform,
15 | ScaleYTransform,
16 | TranslateXTransform,
17 | TranslateYTransform,
18 | SkewXTransform,
19 | SkewYTransform,
20 | } from 'react-native'
21 | import {withAnchorPoint, Point, Size} from 'react-native-anchor-point'
22 |
23 | interface IProps extends ViewProps {
24 | rotateX?: string | undefined
25 | rotateY?: string | undefined
26 | rotateZ?: string | undefined
27 | anchorPoint: Point
28 | }
29 |
30 | const CARD_WIDTH = 60
31 | const CARD_HEIGHT = 80
32 |
33 | export default class TestCard extends React.Component {
34 | getTransform = () => {
35 | // why perspective: 400 useless when put it at last
36 | let transformValues: (
37 | | PerpectiveTransform
38 | | RotateTransform
39 | | RotateXTransform
40 | | RotateYTransform
41 | | RotateZTransform
42 | | ScaleTransform
43 | | ScaleXTransform
44 | | ScaleYTransform
45 | | TranslateXTransform
46 | | TranslateYTransform
47 | | SkewXTransform
48 | | SkewYTransform
49 | )[] = [{perspective: 400}]
50 |
51 | if (this.props.rotateX) {
52 | transformValues.push({rotateX: this.props.rotateX})
53 | }
54 |
55 | if (this.props.rotateY) {
56 | transformValues.push({rotateY: this.props.rotateY})
57 | }
58 |
59 | if (this.props.rotateZ) {
60 | transformValues.push({rotateZ: this.props.rotateZ})
61 | }
62 |
63 | let transform = {
64 | transform: transformValues,
65 | }
66 | return withAnchorPoint(transform, this.props.anchorPoint, {
67 | width: CARD_WIDTH,
68 | height: CARD_HEIGHT,
69 | })
70 | }
71 |
72 | render() {
73 | const {anchorPoint} = this.props
74 | const top = anchorPoint.y * CARD_HEIGHT
75 | const left = anchorPoint.x * CARD_WIDTH
76 |
77 | return (
78 |
79 |
80 |
81 |
82 | {' '}
83 | (x: {anchorPoint.x}, y: {anchorPoint.y}
84 |
85 |
86 | )
87 | }
88 | }
89 |
90 | const styles = StyleSheet.create({
91 | background: {
92 | marginLeft: CARD_WIDTH,
93 | width: CARD_WIDTH,
94 | height: CARD_HEIGHT,
95 | backgroundColor: '#a6abaa',
96 | },
97 | anchorPoint: {
98 | width: 8,
99 | height: 8,
100 | backgroundColor: '#cc3b92',
101 | position: 'absolute',
102 | },
103 | blockBlue: {
104 | flex: 1,
105 | backgroundColor: '#03fcd3',
106 | },
107 | text: {
108 | position: 'absolute',
109 | left: 20,
110 | top: 20,
111 | color: '#bd488b',
112 | },
113 | })
114 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/TestAnimatedCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | StyleSheet,
4 | View,
5 | Animated,
6 | ViewProps,
7 | Text,
8 | RotateXTransform,
9 | RotateYTransform,
10 | PerpectiveTransform,
11 | RotateTransform,
12 | RotateZTransform,
13 | ScaleTransform,
14 | ScaleXTransform,
15 |
16 | ScaleYTransform,
17 | TranslateXTransform,
18 | TranslateYTransform,
19 | SkewXTransform,
20 | SkewYTransform,
21 | Easing,
22 | } from 'react-native';
23 | import { withAnchorPoint, Point, Size } from 'react-native-anchor-point';
24 |
25 |
26 | interface IProps extends ViewProps {
27 | rotateOnXAxis?: boolean;
28 | rotateOnYAxis?: boolean;
29 | rotateOnZAxis?: boolean;
30 | anchorPoint: Point;
31 | }
32 |
33 | const CARD_WIDTH = 60;
34 | const CARD_HEIGHT = 60;
35 |
36 | export default class TestAnimatedCard extends React.Component {
37 | _rotateAnimatedValue = new Animated.Value(0);
38 | _animation: any
39 |
40 | componentDidMount() {
41 | this._animation = Animated
42 | .loop(
43 | Animated.timing(this._rotateAnimatedValue, {
44 | toValue: 1,
45 | duration: 1500,
46 | useNativeDriver: true,
47 | easing: Easing.cubic
48 | }),
49 | { iterations: 1000 },
50 | )
51 | .start();
52 | }
53 |
54 | componentWillUnmount() {
55 | if(this._animation) {
56 | this._animation.stop();
57 | this._animation = null
58 | }
59 | }
60 |
61 | getTransform = () => {
62 | // why perspective: 400 useless when put it at last
63 | let transformValues= [{ perspective: 400 }];
64 | const rotateValue = this._rotateAnimatedValue.interpolate({
65 | inputRange: [0, 1],
66 | outputRange: ['0deg', '360deg'],
67 | })
68 |
69 | const {rotateOnXAxis, rotateOnYAxis, rotateOnZAxis} = this.props;
70 | if (rotateOnXAxis) {
71 | transformValues.push({ rotateX: rotateValue });
72 | }
73 |
74 | if (rotateOnYAxis) {
75 | transformValues.push({ rotateY: rotateValue });
76 | }
77 |
78 | if (rotateOnZAxis) {
79 | transformValues.push({ rotateZ: rotateValue });
80 | }
81 |
82 | let transform = {
83 | transform: transformValues,
84 | };
85 | return withAnchorPoint(transform, this.props.anchorPoint, { width: CARD_WIDTH, height: CARD_HEIGHT });
86 | };
87 |
88 | render() {
89 | const { anchorPoint } = this.props;
90 | const top = anchorPoint.y * CARD_HEIGHT;
91 | const left = anchorPoint.x * CARD_WIDTH;
92 |
93 | return (
94 |
95 |
96 |
97 |
98 | x: {anchorPoint.x}, y: {anchorPoint.y}
99 |
100 |
101 | );
102 | }
103 | }
104 |
105 | const styles = StyleSheet.create({
106 | background: {
107 | margin: 10,
108 | width: CARD_WIDTH,
109 | height: CARD_HEIGHT,
110 | backgroundColor: '#a6abaa',
111 | },
112 | anchorPoint: {
113 | width: 8,
114 | height: 8,
115 | backgroundColor: '#cc3b92',
116 | position: 'absolute',
117 | },
118 | blockBlue: {
119 | flex: 1,
120 | backgroundColor: '#03fcd3',
121 | },
122 | text: {
123 | position: 'absolute',
124 | top: CARD_HEIGHT/2,
125 | color: '#bd488b',
126 | fontSize: 11
127 | },
128 | });
129 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/TestCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, View, Animated, ViewProps, Text, RotateXTransform, RotateYTransform,
3 | PerpectiveTransform, RotateTransform, RotateZTransform, ScaleTransform,
4 | ScaleXTransform, ScaleYTransform, TranslateXTransform, TranslateYTransform,
5 | SkewXTransform, SkewYTransform } from 'react-native';
6 | import { withAnchorPoint, Point, Size } from 'react-native-anchor-point';
7 |
8 | interface IProps extends ViewProps {
9 | rotateX?: string | undefined;
10 | rotateY?: string | undefined;
11 | rotateZ?: string | undefined;
12 | anchorPoint: Point;
13 | }
14 |
15 | const CARD_WIDTH = 200;
16 | const CARD_HEIGHT = 200;
17 |
18 | export default class TestCard extends React.Component {
19 | getTransform = () => {
20 | // why perspective: 400 useless when put it at last
21 | let transformValues:
22 | (PerpectiveTransform
23 | | RotateTransform
24 | | RotateXTransform
25 | | RotateYTransform
26 | | RotateZTransform
27 | | ScaleTransform
28 | | ScaleXTransform
29 | | ScaleYTransform
30 | | TranslateXTransform
31 | | TranslateYTransform
32 | | SkewXTransform
33 | | SkewYTransform)[] = [{ perspective: 400 }];
34 |
35 | if (this.props.rotateX) {
36 | transformValues.push({rotateX: this.props.rotateX})
37 | }
38 |
39 | if (this.props.rotateY) {
40 | transformValues.push({rotateY: this.props.rotateY})
41 | }
42 |
43 | if (this.props.rotateZ) {
44 | transformValues.push({rotateZ: this.props.rotateZ})
45 | }
46 |
47 | let transform = {
48 | transform: transformValues,
49 | };
50 | return withAnchorPoint(transform, this.props.anchorPoint, { width: CARD_WIDTH, height: CARD_HEIGHT });
51 | };
52 |
53 | render() {
54 | const {anchorPoint} = this.props;
55 | const top = anchorPoint.y * CARD_HEIGHT;
56 | const left = anchorPoint.x * CARD_WIDTH;
57 |
58 | return (
59 |
60 |
61 |
62 | (x: {anchorPoint.x}, y: {anchorPoint.y}
63 |
64 | );
65 | }
66 | }
67 |
68 | const styles = StyleSheet.create({
69 | background: {
70 | margin: 50,
71 | width: CARD_WIDTH,
72 | height: CARD_HEIGHT,
73 | backgroundColor: '#a6abaa',
74 | },
75 | anchorPoint: {
76 | width: 8,
77 | height: 8,
78 | backgroundColor: '#cc3b92',
79 | position: 'absolute',
80 | },
81 | blockBlue: {
82 | flex: 1,
83 | backgroundColor: '#03fcd3',
84 | },
85 | text: {
86 | position: 'absolute',
87 | left: 80,
88 | top: 80,
89 | color: '#bd488b',
90 | },
91 | });
92 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "anchor-point-test",
4 | "slug": "anchor-point-test",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "splash": {
9 | "image": "./assets/splash.png",
10 | "resizeMode": "contain",
11 | "backgroundColor": "#ffffff"
12 | },
13 | "updates": {
14 | "fallbackToCacheTimeout": 0
15 | },
16 | "assetBundlePatterns": [
17 | "**/*"
18 | ],
19 | "ios": {
20 | "supportsTablet": true
21 | },
22 | "android": {
23 | "adaptiveIcon": {
24 | "foregroundImage": "./assets/adaptive-icon.png",
25 | "backgroundColor": "#FFFFFF"
26 | }
27 | },
28 | "web": {
29 | "favicon": "./assets/favicon.png"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/exmaple/anchor-point-test/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/exmaple/anchor-point-test/assets/favicon.png
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/exmaple/anchor-point-test/assets/icon.png
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/exmaple/anchor-point-test/assets/splash.png
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "expo": "~43.0.2",
12 | "expo-status-bar": "~1.1.0",
13 | "react": "17.0.1",
14 | "react-dom": "17.0.1",
15 | "react-native": "0.64.3",
16 | "react-native-web": "0.17.1",
17 | "react-native-anchor-point": "~1.0.6"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.12.9",
21 | "@types/react": "~17.0.21",
22 | "@types/react-native": "~0.64.12",
23 | "typescript": "~4.3.5"
24 | },
25 | "private": true
26 | }
27 |
--------------------------------------------------------------------------------
/exmaple/anchor-point-test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import type { TransformsStyle } from "react-native";
2 |
3 | export interface Point {
4 | x: number;
5 | y: number;
6 | }
7 |
8 | export interface Size {
9 | width: number;
10 | height: number;
11 | }
12 |
13 | const isValidSize = (size: Size): boolean => {
14 | return size && size.width > 0 && size.height > 0;
15 | };
16 |
17 | const defaultAnchorPoint = { x: 0.5, y: 0.5 };
18 |
19 | export const withAnchorPoint = (transform: TransformsStyle, anchorPoint: Point, size: Size) => {
20 | if(!isValidSize(size)) {
21 | return transform;
22 | }
23 |
24 | let injectedTransform = transform.transform;
25 | if (!injectedTransform) {
26 | return transform;
27 | }
28 |
29 | if (anchorPoint.x !== defaultAnchorPoint.x && size.width) {
30 | const shiftTranslateX = [];
31 |
32 | // shift before rotation
33 | shiftTranslateX.push({
34 | translateX: size.width * (anchorPoint.x - defaultAnchorPoint.x),
35 | });
36 | injectedTransform = [...shiftTranslateX, ...injectedTransform];
37 | // shift after rotation
38 | injectedTransform.push({
39 | translateX: size.width * (defaultAnchorPoint.x - anchorPoint.x),
40 | });
41 | }
42 |
43 | if (!Array.isArray(injectedTransform)) {
44 | return { transform: injectedTransform };
45 | }
46 |
47 | if (anchorPoint.y !== defaultAnchorPoint.y && size.height) {
48 | let shiftTranslateY = [];
49 | // shift before rotation
50 | shiftTranslateY.push({
51 | translateY: size.height * (anchorPoint.y - defaultAnchorPoint.y),
52 | });
53 | injectedTransform = [...shiftTranslateY, ...injectedTransform];
54 | // shift after rotation
55 | injectedTransform.push({
56 | translateY: size.height * (defaultAnchorPoint.y - anchorPoint.y),
57 | });
58 | }
59 |
60 | return { transform: injectedTransform };
61 | };
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-anchor-point",
3 | "version": "1.0.6",
4 | "description": "I provide a util function to inject anchor point, or called pivot point into your transform object, to achieve 3D transform animation in React-Native. This is similar to web transform-origin in React-Native. ",
5 | "main": "index.ts",
6 | "repository": "https://github.com/sueLan/react-native-anchor-point.git",
7 | "keywords": [
8 | "react-native",
9 | "transform origin",
10 | "3d transform",
11 | "anchor point",
12 | "pivot point"
13 | ],
14 | "author": "RongyanZheng ",
15 | "license": "MIT",
16 | "private": false,
17 | "dependencies": {},
18 | "devDependencies": {
19 | "@typescript-eslint/eslint-plugin": "^2.33.0",
20 | "@typescript-eslint/parser": "^2.33.0",
21 | "eslint": "^7.0.0",
22 | "eslint-plugin-react": "^7.20.0",
23 | "react-native": "^0.62.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/res/anchor_point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/res/anchor_point.png
--------------------------------------------------------------------------------
/res/external_point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/res/external_point.png
--------------------------------------------------------------------------------
/res/rotate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/res/rotate.gif
--------------------------------------------------------------------------------
/res/rotateXY.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/res/rotateXY.gif
--------------------------------------------------------------------------------
/res/rotateZ.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sueLan/react-native-anchor-point/bf6b6a4e9037c41364993fed5631a8cc946f7324/res/rotateZ.gif
--------------------------------------------------------------------------------