網頁前端

互動&動畫的幾種姿勢

Gore Wang , 2020 @ HAPPY CODING

Email:gore.wang.f2e@gmail.com

website: https://gore.wang

Gore Wang

  • 設計具沈浸體驗的操作介面
  • 編織具角色性的敘事體驗
  • 打造能提升生活&工作體驗的產品功能

喜歡打造體驗的UXE

喜歡做動畫的F2E

喜歡做動畫的F2E

享受:

Thinking of Animation as the body language of a product.

(Animation, Interaction) => UX

Functional:功能性

 

Natural:自然感

Character:傳達人性的風格

 
  • Visual Feedback 給予操作性的反饋
  • Show Orientation 給予使用者操作方向的提示
  • 讓使用者能對虛擬世界的操作與現實有所連結

需求情境 => 設計 => 工具

Interaction

微互動:CSS/SVG

  • 無 timeline 性的連續動畫
  • 適合連帶被整合在樣式層
  • 可以實現預載動畫
 
  • 無法做連續性動態
  • 當狀態在網頁初始化就被套用無法產成 Transition 效果
.button {
  transition: transform 0.3s ease-out;
  &:hover{
    transform: scale(1.1) translateY(-5%);
  }
}

.man{
  animation: ani-jump 0.5s cubic-bezier(0,.69,1,.37);
}

@keyframes ani-jump{
  from,to {
    transform: 0%;
  }
  50% {
    transform: 50%;
  }
}

CSS Transition

CSS Animation

  • 無法被自然中斷,覆蓋或拔掉都會因為初始化造成閃動

SVG SMIL

<defs>
  <pattern id="p1" patternUnits="userSpaceOnUse" width="20" height="20">
    <g>
      {isAnimate && (
        <animateTransform
          id="animate-pattern-g-word"
          attributeName="transform"
          dur="0.6s"
          type="translate"
          from="0,0"
          to="0,20"
          repeatCount="indefinite"
          additive="sum"
        />
      )}
      <rect width="20" height="10" fill="#d2a708" x="0" y="-20"></rect>
      <rect width="20" height="10" fill="#d2a708" x="0" y="0"></rect>
    </g>
  </pattern>
  <pattern id="p2" xlinkHref="#p1" patternTransform="rotate(43)" />
  <pattern id="p3" xlinkHref="#p1" patternTransform="rotate(40)" />
  <pattern id="p4" xlinkHref="#p1" patternTransform="rotate(45)" />
</defs>

利用SVG特性來實做動畫

偷跑介紹

問題!

不具有caback的特性做細部交互

操作 SVG 特性的 Library

(可初始化, 狀態機驅動, Callback性) => 進階互動

  • 解決 CSS/SVG 的侷限
    • Transition 必須在初始值狀態先被初始化。
    • Animation 必定有頭尾,所以無法被即時性插拔,會造成閃動。
 
  • Callback 帶來組建間的進階互動性
 
 

更進一步

react-spring

bring your components to "life"

with simple spring animation primitives

  • mass
  • tension
  • friction
  • velocity
  • duration
  • easing
  • duration
  • easing

useSpring

react-spring

  • 創造自然具生命性的動態
  • 可以狀態驅動
  • 可以做到 timeline 形式動畫,且可被打斷

以 react-transition-group 為例

<Route
  ...
  children={({ match, ...rest }) => (
    <Transition
      in={match !== null}
      addEndListener={(n, done) => {
        if (match !== null) { // route match 到這頁
          dispatch(actTransitionStart()); // 限制唯有換頁動態結束後才能再次觸發換頁
          animateWillEnter(n, () => {
            dispatch(actTransitionEnd());
            done(); // 告知 Transition 動態已完成,可以將離開的 Page 正式從 DOM 移除
          });
        } else {
          animateWillLeave(n, done);
        }
      }}
    >
      <PageComponent ... />
    </Transition>
  )}
/>

Transition Group

實現物件 Fade In/Out 間的插拔

即時性互動

反饋使用者具連續性的操作

Instant response and constant redirection

  • 不是我們讓介面流動,而是我們讓自己的操作可以流動,介面只是反映流動的操作
  • 尋找 Delay 的各個細節

將熟悉的物理性投影在虛擬世界

Fluid:流體 => 舒服、自然 => 物理預測性

Prediction Transition

可預期理解的動態

Projection 動量投射

// adapted from Apple's "Designing Fluid Interfaces" talk 
// startVelocity = px/ms
const projection = (startVelocity, decelerationRate=0.998) =>
  startVelocity * decelerationRate / (1 - decelerationRate)
  
// example usage
const velocityY = 1.2 // in px/ms
const projectedEndpoint = projection(velocityY) // 679px

react-gesture

const bind = useXXXX(state => {
  const {
    event,       // the source event
    xy,          // [x,y] values (pointer position or scroll offset)
    previous,    // previous xy
    initial,     // xy value when the gesture started
    movement,    // last gesture offset (xy - initial)
    delta,       // movement delta (movement - previous movement)
    offset,      // offset since the first gesture
    lastOffset,  // offset when the last gesture started
    vxvy,        // momentum of the gesture per axis
    velocity,    // absolute velocity of the gesture
    distance,    // offset distance
    direction,   // direction per axis
    startTime,   // gesture start time
    elapsedTime, // gesture elapsed time
    timeStamp,   // timestamp of the event
    first,       // true when it's the first event
    last,        // true when it's the last event
    active,      // true when the gesture is active
    memo,        // value returned by your handler on its previous run
    cancel,      // function you can call to interrupt some gestures
    canceled,    // whether the gesture was canceled (drag and pinch)
    down,        // true when a mouse button or touch is down
    buttons,     // number of buttons pressed
    touches,     // number of fingers touching the screen
    args,        // arguments you passed to bind
    ctrlKey,     // true when control key is pressed
    altKey,      // "      "  alt     "      "
    shift,       // "      "  shift   "      "
    metaKey,     // "      "  meta    "      "
    dragging,    // is the component currently being dragged
    moving,      // "              "              "  moved
    scrolling,   // "              "              "  scrolled
    wheeling,    // "              "              "  wheeled
    pinching     // "              "              "  pinched
  } = state
})

react-gesture + react-spring

放大為互動的效果幅度

賦予使用者滿足感

  • Scroll
  • Bounce
  • Robber Band
```js
const rubberBand = (distance, dimension, constant = 0.55) => 
(distance * dimension * constant) /
(dimension + constant * distance)

const rubberBandClamp = (min, max, delta, constant) => 
	delta < min 
	? -rubberBand(min - delta, max - min, constant) + min
	:delta > max
	 ? rubberBand(delta - max, max - min, constant) + max
	 : delta 
```

Performance

  • Always Transform
  • 盡一切所能確保你線程運算不會將畫面更新率阻斷降低到肉眼所見
 

Express by Animation

據敘事性的動畫

 

  • Character:人性與風格的投影
    • Express a brand's personality

「連續性」的動畫

   tl = gsap
      .timeline({
        onComplete,
      })
      .add(animateCardEntry(refs))
      .add(animateHatFlyUp(refs))
      .add(animateQRCodeZoomAndFind(refs), "-=0.3")
      .add(animateQRManLeftHand(refs))
      .add(animateQRManRightHand(refs, setIsGWordAnimate))
      .add(animateQRCodeOk(refs))
      .add(animateCardLeave(refs))
export default function animateQRManRightHand(
  {
    refHandRight,
    refRiseHand,
  },
  setIsGWordAnimate
) {
  const initHandPathCache = 
  	refHandRight.getAttribute("d")

  const [codeIcon, codeIconUp] = 
  	refIconCodeGroup.childNodes

  return (
    gsap
      .timeline({
      	onComplete: setIsGWordAnimate
      })
      // rise hand
      .to(refHandRight, {
        morphSVG: refRiseHand,
        duration: 0.4,
      })      

...

lotte-web

  • 複雜動畫好維護,減少修改成本
  • 與前端應用的銜接方式方便
  • 即使要做互動形式動畫也沒問題

好維護

與前端銜接交互方便

document
    .getElementById("markpen")
    .addEventListener("click", () => {
        this.anim.playSegments([[105, 522], [328, 522]], true);
    });

即時互動

「擴增情境」的動畫

Canvas / WebGL

  • 擴張情境傳達的場景
  • 突破DOM效能侷限
  • 但有SEO問題

選項:

  • THREE.js、Babylon.js...
  • Aframe、ReactVR...
  • PixiJS
  • ...

Three.js

Pixi.js

CSS 帶給我們的?

CSS Varialbes!

回過頭來:

怎麼做好選擇?

 

並不是不用「工具」就無法做到
而是理解自己的需求&目標

找到可以「滿足&解決」其情境的好「設計」

打造體驗

桌遊

人類圖

風格性產品

實現網頁前端互動&動畫的幾種姿勢

By Gore Wang

實現網頁前端互動&動畫的幾種姿勢

  • 1,178