Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring based animation #101

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react-dom": "16.12.0",
"react-redux": "7.1.3",
"react-scripts": "3.2.0",
"react-spring": "8.0.27",
"react-test-renderer": "16.12.0",
"react-transition-group": "4.3.0",
"redux": "4.0.4",
Expand Down
25 changes: 7 additions & 18 deletions src/components/CompassNeedle.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,27 @@ import { gsap } from 'gsap'
import { Draggable } from 'gsap/Draggable'
import { addRequestEmoji, updateNeedlePosition, setActiveNeedle } from '../store/actions/app'
import { random, getEmojiPosition } from '../utils'
import { autoRotateNeedle } from '../scripts'
import './CompassNeedle.css'

gsap.registerPlugin(Draggable)

class CompassNeedle extends React.Component {
static propTypes = {
id: PropTypes.number.isRequired,
type: PropTypes.oneOf(['request', 'response']).isRequired,
enabled: PropTypes.bool,

// Provided by Redux
// Provided by Redux mapStateToProps
symbols: PropTypes.arrayOf(PropTypes.shape({
emoji: PropTypes.string,
title: PropTypes.string,
text: PropTypes.string
})),
activeNeedle: PropTypes.number,

// Provided by Redux mapDispatchToProps
addRequestEmoji: PropTypes.func,
updateNeedlePosition: PropTypes.func
}

static defaultProps = {
addRequestEmoji: () => {}
}

constructor (props) {
super(props)

Expand All @@ -47,8 +42,7 @@ class CompassNeedle extends React.Component {

// Make needles draggable
gsap.set(el, {
transformOrigin: '2.0vmin',
rotation: random() * 360 // Set at random start position
rotate: random() * 360 // Set at random start position
})

this.draggable = Draggable.create(el, {
Expand Down Expand Up @@ -100,12 +94,7 @@ class CompassNeedle extends React.Component {
componentDidUpdate (prevProps) {
// Activate if this is the currently active needle.
if (this.props.activeNeedle === this.props.id) {
// The response needle will spin automatically
if (this.props.type === 'response') {
autoRotateNeedle(this)
} else {
this.enable()
}
this.enable()
} else {
this.disable()
}
Expand All @@ -118,7 +107,7 @@ class CompassNeedle extends React.Component {

setElementSize = () => {
const circleSize = document.getElementById('ring').getBoundingClientRect().width
const ratio = (this.props.type === 'response') ? 0.425 : 0.355
const ratio = 0.355
this.el.current.style.width = (ratio * circleSize) + 'px'
}

Expand All @@ -145,7 +134,7 @@ class CompassNeedle extends React.Component {

render () {
return (
<div className={`needle needle-${this.props.type}`} ref={this.el } />
<div className="needle needle-request" ref={this.el } />
)
}
}
Expand Down
80 changes: 80 additions & 0 deletions src/components/CompassNeedleResponse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useRef, useEffect } from 'react'
import { connect, useSelector, useDispatch } from 'react-redux'
import { useSpring, animated, config } from 'react-spring'
import { addRequestEmoji, updateNeedlePosition, setActiveNeedle } from '../store/actions/app'
import { random, getUniqueRandomIntegers, getRotation } from '../utils'
import { autoRotateNeedle } from '../scripts'
import symbols from '../symbols.json'
import './CompassNeedle.css'

const INHERENT_NEEDLE_ID = 4
const INHERENT_NEEDLE_TYPE = 'response'

function CompassNeedleResponse (props) {
const symbols = useSelector(state => state.app.symbols)
const activeNeedle = useSelector(state => state.app.activeNeedle)
const dispatch = useDispatch()
const el = useRef(null)
const initialRotation = useRef(random() * 360).current // Set at random start position

// for now auto-rotate 1 number on render.
const numberOfSymbols = symbols.length
const randomNumbers = getUniqueRandomIntegers(numberOfSymbols, 3)
const responseEmojis = randomNumbers.map((num) => symbols[num])
const rotateTo = getRotation(randomNumbers[0], numberOfSymbols)

// always take the longest rotate direction between current and destination emoji
let actualRotateTo = rotateTo
if (Math.abs(initialRotation - actualRotateTo) < 180) {
if (initialRotation > actualRotateTo) {
actualRotateTo = actualRotateTo - 360
} else {
actualRotateTo = actualRotateTo + 360
}
}

const { rotateZ } = useSpring({
from: {
rotateZ: initialRotation,
transform: `rotate(${initialRotation}deg)`
},
to: {
rotateZ: (activeNeedle === INHERENT_NEEDLE_ID) ? actualRotateTo : initialRotation,
transform: `rotate(${actualRotateTo}deg)`
},
config: {
mass: 10,
tension: 55,
friction: 31,
}
})

useEffect(() => {
// Set needle width according to actual circle dimensions
setElementSize()
window.addEventListener('resize', setElementSize)

return () => {
window.removeEventListener('resize', setElementSize)
}
})

function setElementSize () {
const circleSize = document.getElementById('ring').getBoundingClientRect().width
const ratio = 0.425
el.current.style.width = (ratio * circleSize) + 'px'
}


return (
<animated.div
className="needle needle-response"
ref={el}
style={{
transform: rotateZ.interpolate(z => `rotateZ(${z}deg)`)
}}
/>
)
}

export default CompassNeedleResponse
9 changes: 5 additions & 4 deletions src/components/CompassNeedlesContainer.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from 'react'
import CompassNeedle from './CompassNeedle'
import CompassNeedleResponse from './CompassNeedleResponse'
import './CompassNeedlesContainer.css'

export default class CompassNeedlesContainer extends React.Component {
render () {
return (
<div className="compass-needles-container">
<div className="compass-needles">
<CompassNeedle id={1} type="request" />
<CompassNeedle id={2} type="request" />
<CompassNeedle id={3} type="request" />
<CompassNeedle id={4} type="response" />
<CompassNeedle id={1} />
<CompassNeedle id={2} />
<CompassNeedle id={3} />
<CompassNeedleResponse />
<div className="compass-hub" />
</div>
</div>
Expand Down