前端網頁動效

-

作品集拖曳效果怎麼做?放在個人網站超吸睛!

this.web

前陣子看到一個工程師的個人網站做了這種拖曳作品集的特效,覺得非常有趣且吸睛,研究一下發現其實可以用一些套件快速做到這樣的效果,馬上看看是怎麼做的吧!

前置準備 - 安裝 React + Framer Motion

今天我們會用 React + Framer Motion 來製作這個特效。

可以先用 vite 來快速建置 React 的開發環境:

npm create vite@latest my-react-app -- --template react

執行完成後就依序執行以下指令,分別是:

  1. cd my-react-app : 移動到 my-react-app 資料夾
  2. npm install : 安裝相關依賴
  3. npm run dev : 執行這個 app
cd my-react-app
npm install
npm run dev

接著打開 http://localhost:5173 就會看到這個畫面

Vite + React

並且也可以看到你的資料夾結構,如果只是想練習這個特效,直接改 App.jsx 就好

資料夾結構

下一步就是安裝 Framer Motion,直接在終端機打上以下指令就好:

npm install motion

引入圖片 & 製作 container

接著我們可以將圖片存在 ./public/drag-img/ 這個資料夾,並利用陣列的方式儲存圖片的位置,這樣等一下直接用 Array.map 的方式就可以渲染出所有的圖片。

const images = [
  '/drag-img/image-1.png',
  '/drag-img/image-2.png',
  '/drag-img/image-3.png',
  '/drag-img/image-4.png',
  '/drag-img/image-5.png',
  '/drag-img/image-6.png',
  '/drag-img/image-7.png',
  '/drag-img/image-8.png',
  '/drag-img/image-9.png',
  '/drag-img/image-10.png',
  '/drag-img/image-11.png',
  '/drag-img/image-12.png',
  '/drag-img/image-13.png',
  '/drag-img/image-14.png',
];

接著製作等等要放圖片的容器,這邊用 useRef 來儲存 container,因為等等要抓到 container 的寬高,讓圖片能夠保持在這個 container 裡面。

export default function DragImg() {
  const containerRef = useRef(null);

  return (
    <div
      ref={containerRef}
      className='drag-img__container'
    >
      {/* ... */}
    </div>
  );
}

並稍微調整 container 的 style

.drag-img__container {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: #f0f0f0;
}

渲染圖片 & 隨機圖片位置

利用剛剛的 images 陣列來渲染出所有的圖片,注意這邊是用 motion.img 的 tag,這是 framer motion 的用法,這樣等等才可以使用 framer motion 的功能。

export default function DragImg() {
  const containerRef = useRef(null);

  return (
    <div
      ref={containerRef}
      className='drag-img__container'
    >
      {images.map((src, index) => (
        <motion.img
          key={index}
          src={src}
          className='drag-img__img'
          alt={`Image ${index + 1}`}
        />
      ))}
    </div>
  );
}
.drag-img__img {
  width: 200px;
  aspect-ratio: 4/3;
  object-fit: contain;
  padding: 4px;
  position: absolute;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 6px;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
  cursor: grab;
}

稍微調整圖片的寬度、比例,並讓他的 positionabsolute,其他就是一些小裝飾,例如 paddingshadow 等等,現在所有的圖片都會在右上角,因為我們還沒調整他們的位置

圖片重疊在左上角

接著可以利用 JavaScript 來隨機圖片的位置,順便隨機旋轉的角度,讓他有種散落在整個 container 的感覺。

{images.map((src, index) => (
  <motion.img
    key={index}
    src={src}
    className='drag-img__img'
    alt={`Image ${index + 1}`}
    style={{
      top: `${Math.random() * (window.innerHeight - 200)}px`,
      left: `${Math.random() * (window.innerWidth - 150)}px`,
      rotate: `${Math.random() * 40 - 20}deg`,
    }}
  />
))}
圖片隨機位置

製作拖曳效果

接著是重頭戲,我們要利用 Framer Motion 來製作拖曳效果,其實非常簡單,因為 Framer Motion 已經有內建 drag 的屬性,所以我們只要在 motion.img 加上 drag 屬性就完成了~

{images.map((src, index) => (
  <motion.img
    // ...
    drag
    dragConstraints={containerRef}
    whileDrag={{ scale: 1.1, rotate: 0 }}
  />
))}

這邊加上 dragConstraints 讓他只能在 container 裡面拖移,不會跑到外面。並利用 whileDrag 來控制拖曳時的 style。

完整程式碼

到這邊就完全搞定了,其實非常簡單!以下附上全部的程式碼:

import { useRef } from 'react';
import { motion } from 'framer-motion';

const images = [
  '/drag-img/image-1.png',
  '/drag-img/image-2.png',
  '/drag-img/image-3.png',
  '/drag-img/image-4.png',
  '/drag-img/image-5.png',
  '/drag-img/image-6.png',
  '/drag-img/image-7.png',
  '/drag-img/image-8.png',
  '/drag-img/image-9.png',
  '/drag-img/image-10.png',
  '/drag-img/image-11.png',
  '/drag-img/image-12.png',
  '/drag-img/image-13.png',
  '/drag-img/image-14.png',
];

export default function DragImg() {
  const containerRef = useRef(null);

  return (
    <div
      ref={containerRef}
      className='drag-img__container'
    >
      {images.map((src, index) => (
        <motion.img
          key={index}
          src={src}
          className='drag-img__img'
          alt={`Image ${index + 1}`}
          style={{
            top: `${Math.random() * (window.innerHeight - 200)}px`,
            left: `${Math.random() * (window.innerWidth - 150)}px`,
            rotate: `${Math.random() * 40 - 20}deg`,
          }}
          drag
          dragConstraints={containerRef}
          whileDrag={{ scale: 1.1, rotate: 0 }}
        />
      ))}
    </div>
  );
}
.drag-img__container {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: #f0f0f0;
}

.drag-img__img {
  width: 200px;
  aspect-ratio: 4/3;
  object-fit: contain;
  padding: 4px;
  position: absolute;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 6px;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
  cursor: grab;
}

參考連結

作者網站:https://www.yihui.work/

Vite : Getting Started | Vite

React : React

Framer Motion : Quick start | Motion for React (prev Framer Motion)

相關系列文章