react-spring: Render-props api

2020-06-01

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)와 비슷하다.

출처: react-spring-examples

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가 존재한다.

PropertyTypeRequiredDefaultDescription
keysfn/undefined/anyfalseitem => itemitem의 keys(React에 넘겨주는 key와 동일) items를 지정한다면, keys는 접근자 함수가 될 수 있음(item => item.key)
fromobjfalse-기준 값
toobjfalse-바뀔 값
itemsany/undefinedtrue-표시 될 items의 배열, 실제 items에 접근해서 특정 값을 넘겨야 할 때 사용
childrenfntrue-하나의 item을 받는 단일 함수-자식(하위)으로 함수 컴포넌트를 리턴함 ex. (item, index) => props => view
reverseboolfalse-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>에서 선언한 opacityx값을 그대로 <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도 사용가능하다.

PropertyTypeRequiredDefaultDescription
keysunionfalseitem => item일반적으로 list에서 React로 넘겨주는 key와 동일한 key. keys는 key-accessor로 함수, 배열 혹은 단일 값으로 지정될 수 있음
uniqueboolfalsefalsetrue 일 때, 동일 한 key를 가진 두 개 이상의 items들이 stack에 공존하도록 허용하는 대신, 한 번만 발생할 수 있다고 강행
resetboolfalsefalse'unique'와 주로 같이 사용되며, true일 때, 현재 값에 적응하는 대신 이미 존재하는 들어오는 items이 다시 시작되도록 강행
initialunionfalse-첫번째 render의 초기 값, 만약 값이 존재한다면 첫 render에서 "from"보다 우선 됨. 첫 mounting transition을 스킵하고 싶다면 "null"로 작성. object나 function을 가질 수 있음 (item => object)
fromunionfalse-기준 값 (from => enter), 혹은 item => values
enterunionfalse-들어오는(새로운) (entering) elements에 적용되는 값, 혹은 item => values
leaveunionfalse-없어지는(leaving) elements에 적용되는 값, 혹은 item => values
updateunionfalse-들어오지도 나가지도 않는 element에 적용되는 값(이 값을 사용하여 현재 elements를 업데이트 할 수 있음) , 혹은 item => values
trailnumberfalse-지연되는 시간(초 단위)
configunionfalse-Spring의 config 값, 혹은 각각의 keys에 해당: fn( (item, type) => key => config) 혹은 fn ( (item, type) => config) , "type"은 enter, leave, update가 될 수 있음
onDestoryedfnfalse-transition이 끝날 때 콜백
itemsuniontrue-표시할 items의 배열(또는 모든 타입의 단일 item) 이며, Transition에 의해 변경 사항을 탐지하는 주요 수단으로 사용
childrenfntrue-하나의 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도 사용가능하다.

PropertyTypeRequiredDefaultDescription
statestringfalse__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

PropertyTypeRequiredDefaultDescription
configobjectfalseconfig.slowSpring config (선택)
scrollingboolfalsetrue스크롤 가능 여부
horizontalboolfalsefalse스크롤의 가로, 세로 결정
pagesnumbertrue-각 page에 100%를 차지하는 container의 내부 공간(space) 설정

2. ParallaxLayer

PropertyTypeRequiredDefaultDescription
factornumberfalse1page 사이즈 (1=100%, 1.5= 150%, ...)
offsetnumberfalse0layer가 언제 scroll될 지 결정 (0=start, 1=1page, ...)
speednumberfalse0offset에 따라 layer 변경, 값은 +, - 값이 올수 있음

오역이 있을 수 있습니다. 피드백은 언제나 환영합니다!