react-spring: Render-props api
Render-props api
react-spring 6.0(2018.10)까지는 Render-props api만 있었다. Render-props api도 크게 5가지로 나뉜다.
- Spring: spring 혹은 springs의 데이터가 from: a => to: b로 움직이는 것
- Trail: list의 다른 아이템들이 첫번째 아이템을 따라 움직이는 것
- Transition: list의 아이템들이 추가/제거/업데이트 될 때 animation 발생
- Keyframes: animation들을 연결, 구성, 조율할 수 있는 것
- Parallax: 스크롤을 만드는 것
해당 글은 react-spring 8.0 기준으로 작성되었다.
react-spring의 property는 이전글을 참고하길 바란다.
1. Spring
Spring은 Hooks-api의 useSpring(s)와 비슷하다.
import { Spring, animated } from 'react-spring/renderprops'
const LOREM = `Hello world`
export default class App extends React.Component {
  state = { toggle: true, text: [LOREM] }
  onToggle = () => this.setState(state => ({ toggle: !state.toggle }))
  onAddText = () =>
    this.setState(state => ({ toggle: true, text: [...state.text, LOREM] }))
  onRemoveText = () =>
    this.setState(state => ({ toggle: true, text: state.text.slice(1) }))
  render() {
    const { toggle, text } = this.state
    return (
      <div className="auto-main">
        <button onClick={this.onToggle}>Toggle</button>
        <button onClick={this.onAddText}>Add text</button>
        <button onClick={this.onRemoveText}>Remove text</button>
        <div className="content">
          <Spring
            native
            force
            config={{ tension: 2000, friction: 100, precision: 1 }}
            from={{ height: toggle ? 0 : 'auto' }}
            to={{ height: toggle ? 'auto' : 0 }}
          >
            {props => (
              <animated.div className="item" style={props}>
                {text.map((t, i) => (
                  <p key={i}>{t}</p>
                ))}
              </animated.div>
            )}
          </Spring>
        </div>
      </div>
    )
  }
}
useSpring과 크게 다를게 없기 때문에 바로 Trail로 넘어가겠다.
2. Trail
Trail 역시 useTrail과 비슷한데, native, from, immediate, onReset 등과 같은 spring properties도 사용 가능하며, Trail에서만 쓰이는 Props가 존재한다.
| Property | Type | Required | Default | Description | 
|---|---|---|---|---|
| keys | fn/undefined/any | false | item => item | item의 keys(React에 넘겨주는 key와 동일) items를 지정한다면, keys는 접근자 함수가 될 수 있음(item => item.key) | 
| from | obj | false | - | 기준 값 | 
| to | obj | false | - | 바뀔 값 | 
| items | any/undefined | true | - | 표시 될 items의 배열, 실제 items에 접근해서 특정 값을 넘겨야 할 때 사용 | 
| children | fn | true | - | 하나의 item을 받는 단일 함수-자식(하위)으로 함수 컴포넌트를 리턴함 ex. (item, index) => props => view | 
| reverse | bool | false | - | true일 때, triling 순서가 bottom => top으로 바뀜 | 
codesandbox에 구현되지 않아 코드로만 대체한다.
import { Trail, animated } from 'react-spring/renderprops'
export default class TrailsExample extends React.PureComponent {
  state = {
    toggle: true,
    items: ['item1', 'item2', 'item3', 'item4', 'item5'],
  }
  toggle = () =>
    this.setState(state => ({
      toggle: !state.toggle,
    }))
  render() {
    const { toggle, items } = this.state
    return (
      <div
        style={{
          backgroundColor: '#247BA0',
          position: 'relative',
          width: '100%',
          height: '100%',
        }}
      >
        <Trail
          native
          reverse={toggle}
          initial={null}
          items={items}
          from={{ opacity: 0, x: -100 }}
          to={{ opacity: toggle ? 1 : 0.25, x: toggle ? 0 : 100 }}
        >
          {item => ({ x, opacity }) => (
            <animated.div
              className="box"
              onClick={this.toggle}
              style={{
                opacity,
                transform: x.interpolate(x => `translate3d(${x}%,0,0)`),
              }}
            />
          )}
        </Trail>
      </div>
    )
  }
}
/** css */
.box {
  cursor: pointer;
  position: relative;
  width: 50%;
  height: 20%;
  background-color: #f3ffbd;
  will-change: transform;
}
예시 보러가기 : TRAILS
여기서 items는 움직일 때 보여지는 박스 5칸을 뜻 하며, onClick()이 발동되었을 때 toggle 값이 바뀔 때 animation이 일어난다.
<Trail>에서 선언한 opacity와 x값을 그대로 <animated.div>의 props로 넘겨주기 위해 {item => ({x, opacity}) => ()}로 넘겨준다.
3. Transition
Transition도 useTransition과 같이 lifecycles에 따라 animation이 실행된다.
Transition은 모든 종류의 타입으로 이루어진 items으로 이루어져 있다. 마지막 기본값으로는 item => item인데, items이 key로 자급자족할 수 있는 경우가 많다.(??) items가 추가, 제거, 재정렬, 업데이트 될 때마다 애니메이션화 하는데 도움이 될 것이다.
item과 추가적으로 transition state(enter/leave/update)와 index를 받는 단일 함수-자식(하위)을 제공해야 한다. 전체적인 그림은 이것과 같다: (item, state, index) => props => ReactNode.
이미 삭제된 item을 render할 수 있으니 스코프 밖에서 값을 받는 것보다, 항상 함수로 받는 item들에 의존해야 한다. 하지만 이 item은 fade-out이 완료될 때 까지 Transition 내부에 보관 된다. 이는 routes에 굉장히 중요하다(?).
요약하자면, 필요한 경우 keys를 items에 표시하고, props로 넘겨주어 animation에 적용하면 된다.
Transition Properties
native, from, immediate, onRest 등의 spring의 properties도 사용가능하다.
| Property | Type | Required | Default | Description | 
|---|---|---|---|---|
| keys | union | false | item => item | 일반적으로 list에서 React로 넘겨주는 key와 동일한 key. keys는 key-accessor로 함수, 배열 혹은 단일 값으로 지정될 수 있음 | 
| unique | bool | false | false | true 일 때, 동일 한 key를 가진 두 개 이상의 items들이 stack에 공존하도록 허용하는 대신, 한 번만 발생할 수 있다고 강행 | 
| reset | bool | false | false | 'unique'와 주로 같이 사용되며, true일 때, 현재 값에 적응하는 대신 이미 존재하는 들어오는 items이 다시 시작되도록 강행 | 
| initial | union | false | - | 첫번째 render의 초기 값, 만약 값이 존재한다면 첫 render에서 "from"보다 우선 됨. 첫 mounting transition을 스킵하고 싶다면 "null"로 작성. object나 function을 가질 수 있음 (item => object) | 
| from | union | false | - | 기준 값 (from => enter), 혹은 item => values | 
| enter | union | false | - | 들어오는(새로운) (entering) elements에 적용되는 값, 혹은 item => values | 
| leave | union | false | - | 없어지는(leaving) elements에 적용되는 값, 혹은 item => values | 
| update | union | false | - | 들어오지도 나가지도 않는 element에 적용되는 값(이 값을 사용하여 현재 elements를 업데이트 할 수 있음) , 혹은 item => values | 
| trail | number | false | - | 지연되는 시간(초 단위) | 
| config | union | false | - | Spring의 config 값, 혹은 각각의 keys에 해당: fn( (item, type) => key => config) 혹은 fn ( (item, type) => config) , "type"은 enter, leave, update가 될 수 있음 | 
| onDestoryed | fn | false | - | transition이 끝날 때 콜백 | 
| items | union | true | - | 표시할 items의 배열(또는 모든 타입의 단일 item) 이며, Transition에 의해 변경 사항을 탐지하는 주요 수단으로 사용 | 
| children | fn | true | - | 하나의 item을 받는 단일 함수-자식(하위)으로 함수 컴포넌트를 리턴함 ex. (item, index) => props => view | 
이것도 이상하게 codesandbox에서 구현되지 않아 코드만 올린다.
import React from 'react'
import { Transition, animated } from 'react-spring/renderprops'
import './styles.css'
export default class App extends React.PureComponent {
  state = { show: true }
  toggle = e => this.setState(state => ({ show: !state.show }))
  render() {
    return (
      <div className="reveals-main" onClick={this.toggle}>
        <Transition
          native
          items={this.state.show}
          from={{ position: 'absolute', overflow: 'hidden', height: 0 }}
          enter={[{ height: 'auto' }]}
          leave={{ height: 0 }}
        >
          {show =>
            show && (props => <animated.div style={props}>hello</animated.div>)
          }
        </Transition>
      </div>
    )
  }
}
/** css */
.reveals-main {
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.reveals-main > div {
  font-weight: 800;
  font-size: 4em;
  will-change: transform, opacity;
  overflow: hidden;
}
예시 보러가기 : SIMPLE REVEALS
onClick()에 따라 toggle값이 바뀌면서 'Hello'란 글자가 사라졌다 보여진다.
<Transition
  native
  items={this.state.show}
  from={{ position: 'absolute', overflow: 'hidden', height: 0 }}
  enter={[{ height: 'auto' }]}
  leave={{ height: 0 }}
>
  {show => show && (props => <animated.div style={props}>hello</animated.div>)}
</Transition>
기본값으로 height: 0으로 되어 있으며, 클릭하면 leave로 바뀌어서 글자가 위에서 밑으로 사라진다. 그 후, 다시 클릭하면 enter가 실행되어 글자가 밑에서 올라오는 듯한 animation을 보여준다.
4. Keyframes
Keyframes은 springs나 trails의 기능을 확장하는 공장과 같다. 먼저 구현하고 싶은 한 개 이상의 animation을 named-slot으로 정의한다. 알수 없는 모든 props는 interpolate를 이용해 "to"로 설정한다. 이 외에도, from, config, reset 등의 모든 spring props를 사용할 수 있다. 이는 특별한 "state" prop을 가진 컴포넌트를 생성하고, 만들어 둔 slot 중 하나로 그 값을 받는다. 그러면 그것이 실행되면서 animation을 일으킬 것이다.
slot은 아래와 같은 것을 가질 수 있다:
- properties를 가진 object
- animation이 연결된(chained) objects의 배열
- loop를 만들 수 있는 함수
요약하자면, named-slots으로 Keyframe-object를 정의해야 한다.
Keyframes Properties
resulting 컴포넌트는 native, from, immediate, onRest 등의 spring properties도 사용가능하다.
| Property | Type | Required | Default | Description | 
|---|---|---|---|---|
| state | string | false | __default | 활성화된 slot의 이름 | 
codesandbox에서 delay를 넣으면 실행이 안돼서 빼고 가져왔더니 animation이 온전하지 못하다. 원본을 보고 싶다면 SCRIPTED KEYFRAMES 여기서 확인하는 것을 추천한다.
import { Keyframes, animated, config } from 'react-spring/renderprops'
// import delay from 'delay'
const Content = Keyframes.Spring(async next => {
  // None of this will cause React to render, the component renders only once :-)
  while (true) {
    await next({
      from: { opacity: 0, width: 50, height: 50, background: 'black' },
      opacity: 1,
      width: 80,
      height: 80,
      background: 'tomato',
    })
    await next({
      from: { left: '0%' },
      left: '70%',
      background: 'seagreen',
    })
    next({
      from: { top: '0%' },
      top: '40%',
      background: 'plum',
      config: config.wobbly,
    })
    // await delay(2000) // don't wait for the animation above to finish, go to the next one in 2s
    await next({ left: '0%', background: 'hotpink' })
    await next({
      top: '0%',
      background: 'teal',
    })
    await next({
      opacity: 0,
      width: 40,
      height: 40,
      background: 'black',
    })
  }
})
export default class App extends React.Component {
  render() {
    return (
      <div
        style={{
          width: '100%',
          height: '100%',
          overflow: 'hidden',
          background: 'aquamarine',
          padding: 10,
        }}
      >
        <Content native>
          {props => (
            <animated.div
              style={{ position: 'relative', borderRadius: '50%', ...props }}
            />
          )}
        </Content>
      </div>
    )
  }
}
Keyframes를 이용해 모든 Spring들을 묶어준다. 그리고 await를 이용해 animation이 일어날 순서를 정의한다. 위에서 정의한 모든 props를 <Content> 내부에서 가져와서 사용한다.
5. Parallax
Parallax는 시차(視差: 관측 위치에 따른 물체의 위치나 방향의 차이)란 뜻으로 주로 scroll container를 만들어 준다. 그리고 ParallaxLayers에 값을 넣어 offsets과 speeds등을 따라 움직일 수 있다.
Parallax Properties
1. Parallax
| Property | Type | Required | Default | Description | 
|---|---|---|---|---|
| config | object | false | config.slow | Spring config (선택) | 
| scrolling | bool | false | true | 스크롤 가능 여부 | 
| horizontal | bool | false | false | 스크롤의 가로, 세로 결정 | 
| pages | number | true | - | 각 page에 100%를 차지하는 container의 내부 공간(space) 설정 | 
2. ParallaxLayer
| Property | Type | Required | Default | Description | 
|---|---|---|---|---|
| factor | number | false | 1 | page 사이즈 (1=100%, 1.5= 150%, ...) | 
| offset | number | false | 0 | layer가 언제 scroll될 지 결정 (0=start, 1=1page, ...) | 
| speed | number | false | 0 | offset에 따라 layer 변경, 값은 +, - 값이 올수 있음 | 
오역이 있을 수 있습니다. 피드백은 언제나 환영합니다!