iT邦幫忙

2022 iThome 鐵人賽

DAY 11
1

Day11 自己做一個價值幾十萬的動態網站

第十一課:useRef與gsap介紹與應用,hover彈跳comment視窗與Slider 滑動banner特效

useRef與gsap介紹與應用 hover彈跳comment視窗

useRef一樣跟useState等等是React Hooks其中一個,所以使用上也要先

import React, { useRef } from 'react'

而useRef的概念也跟useState有點像,都屬hook系列,hook出一整個div。一開始都會給他一個初始值,比如說,下方設立一個變數comments為null值

 let comments = useRef(null)

useRef的用途很廣可以是為了做一些網頁的互動特效或是功能,所以就跟useState來設置打開視窗等等的一樣,useRef也可以專門設置跳出視窗,但這兩個差別是useState在做出的特效上是根據useState得初始值,如按鈕會有onClick並通過點擊來更改boolean的值,紀錄開與關去做轉換,詳細的部分在前面實作上我們也練習過很多次了,而useRef可以把div拉成一個變數去做運算跟特效,在操作上配合很多函數可以比useState更活用,也可以想成useState不是專門在做互動特效處理的,如果需要大量的開關特效或是互動,useRef直接拉出區塊作用比較快,而不是useState下去做存取、修改資料等等的,總之這兩個方式一定都可以運用,只是要看情況用哪個比較適當,而下面就會實作用useRef配合gsap來做hover特效(hover就是不用點擊,滑鼠過去就有就顯示了),跟useState的點擊開啟視窗不一樣的操作。

而gsap則是一個可以配合useRef來專門處理動畫與特效的套件,所以一樣必須都先導入

import { gsap } from "gsap";

因為他是專門對網頁特效做處理的套件,所以這邊會來介紹他的gsap.from跟
gsap.to,做動畫一定都會有時間序的問題,所以這邊很有趣的gsap都能幫你做到,


下方就是混用後常見的樣子

變數這邊也可以直接提classname的部分不一定要配合用useRef
這邊有更詳細得官網解釋
https://greensock.com/docs/v3/GSAP/gsap.from()
所以簡單來說我們要用這個方式來做彈跳留言視窗

hotelPage 留言彈跳視窗製作與其div

首先要先製作沒有彈跳動作的視窗設計,剛好我們有預留hotelImgWrapper的空間可以放popupcomment.div

popupcomment.div

<div className="popupcomment" >
  <div className='commentInfo' ref={e => (comments = e)} >
    <button className='commentRate'>
      9.5
    </button>
    傑出<br />
    1,223則評論
  </div>
</div>

然後我這邊的設計目的我想要讓popupcomment這個div是一個感知區域,當他被碰到時會跳出評論,然後commentInfo才是實際的顯示視窗。

然後再設計scss, 然後popupcomment一樣可以配合
position: absolute;與.hotelImgWrapper {position: relative;}讓他用絕對位置定位,跟之前的headerSearchBar一樣做法

.hotelImgWrapper {position: relative;}
.popupcomment {
    position: absolute; //觸碰區
    top: 0px;
    left: 0px;
    z-index: 2; //這個代表圖層至少比下面img照片都還要多一層 
    //就是最上方圖層才不會被蓋住看不見
    height: 300px;
    width: 300px;
    padding: 10px;
    background-color: rgba(31, 30, 30, 0.559);
    .commentInfo {
        display: flex;
        height: 60px;
        width: 250px;
        border-radius: 2px;
        background-color: #fff;
        align-items: center;
        justify-content: flex-start;
        gap: 5px;
        .commentRate {
            margin-left: 5px;//左邊間距空一下
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 20px;
            font-weight: 700;
            height: 40px;
            width: 40px;
            border: none;
            background-color: var(--primary-color);
            border-radius: 5px 5px 5px 0px;
        }
    }
}

好了應該就會看到觸控區的遮罩了,到時候會把那個底色刪除,留下他的區域可以觸控,所以終於可以進到hover功能部分

撰寫hover函數,實際應用在hotelPage留言彈跳視窗

首先先了解整個hover過程,原本要先消失後來hover進去 視窗出現 hover出去 視窗消失 這一套過程,所以先將原本的popupcomment等先隱藏 他沒有底色,跟讓commentInfo整個消失 所以用display:none,來表示觸控區一直都在,但彈跳視窗會消失與出現。

handleHover functions

 const handleHover = e => {
    gsap.to(comments, {
      css: {
        display: "flex",
        opacity: 1,
      },
         ease: "power3.inOut"//拉出來跟css並行
    })
  }
  //離開特效
  const handleHoverExit = e => {
    gsap.to(comments, {
      css: {
        display: "none",
        opacity: 0,  
      },
 ease: "power3.inOut"//拉出來跟css並行
    })
  }

ease: "power3.inOut" 圖上面得 ease: "power3.inOut"放在css內會產生warning,所以製圖上忘記改,但實際上記得以上面這種示範將它拉出來。

配合好函數後,再將它寫入div popupcomment 觸控區 這邊使用onMouseEnter與onMouseOut預設函數來連結到我們的hover函數

<div className="popupcomment" onMouseEnter={e => handleHover(e)} onMouseOut={e => handleHoverExit(e)} >
    <div className='commentInfo' onMouseEnter={e => handleHover(e)} ref={e => (comments = e)} >

commentInfo這邊也寫onMouseEnter是因為 這樣防止滑鼠從popupcomment觸控區滑到commentInfo時會觸發handleHoverExit,而讓視窗消失,因為對popupcomment來說我們滑鼠的確是離開了他這一層到較高一層的commentInfo上,所以離開自己區域都會觸發。
附上github連結
hotel.jsx
hotel.scss

hotelPage 彈跳視窗UI設計 動畫前置作業

首先先來這次要做的跟前面練習過的useState開啟彈跳視窗一樣,只是這次會更進階,我們要來做可以左右點選換照片的彈跳視窗,也就是常見的slider
如:w3shool的基本slider練習
https://www.w3schools.com/w3css/w3css_slideshow.asp


介面UI分析

首先我們可以將slider.div寫在hotel.jsx 的navabar下方,但在hotelContainer的上方內,如圖因為是彈跳視窗以不要影響到其他component為主,slider到時候也會是滿版,所以也要直接寫在hotel.div內,如圖

附上silder.div

<div className="slider">
        <div className="sliderWrapper">
          <div className="wrapperTitle">
          <div className='TitleName'>台南微醺文旅</div>
          <span className="CloseSign">關閉
            <FontAwesomeIcon icon={faXmark}   /></span> 
          </div>
          <div className="wrapperBody">
            <FontAwesomeIcon icon={faAngleLeft} className="arrow"  />
            <img src={photos[0].src} alt="" />
            <FontAwesomeIcon icon={faAngleRight} className="arrow" />
          </div>
        </div>
      </div>

中間photos[0]是先放一張照片準備,並之後會改成變數,當左右滑動箭頭被點擊時,換成該照片的index,所以原理十分的簡單,就是點擊右鍵icon,照片的img Index+1 就換成下一張照片了,點擊左鍵icon,照片的img Index-1 就換成上一張照片了,
並簡單的排版一下
附上slider.scss

.slider {
    position: fixed; //要讓他黏在整個頁面上
    top: 0;
    left: 0;//並重新定義黏在頁面上的起點才能讓下面滿版
    width: 100vw;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.613); //後面黑色遮罩的部分
    z-index: 999; //使他圖層數最高,這樣一定會是在最上面
    display: flex;
    align-items: center;
    justify-content: center;//在水平上下置中一下
    .sliderWrapper {
        border-radius: 2px;
        width: 90%;
        height: 80%;//簡單設置一下視窗的長寬
        background-color: #fff;
    }
}
.wrapperTitle {
    width: 100%;
    height: 10%;
    display: flex;
    justify-content: space-between; //將飯店名與 關閉左右分開
    align-items: center;
    border-bottom: 1px solid lightgray; //底線分隔線
    .TitleName {
        font-size: 20px;//排版一下飯店名
        margin: 0 20px;
    }
    .CloseSign {//排版一下關閉icon等等的
        margin: 0 20px;
        display: flex;
        align-items: center;
        gap: 10px;
        cursor: pointer;
        svg {
            font-size: 30px;
        }
        &:hover {
            color: var(--primary-color);
        }
    }
}
.wrapperBody {
    height: 90%;
    display: flex;
    align-items: center;
    justify-content: center;
    img {
        width: 600px;
        height: 500px;
        border-radius: 5px;
        user-select: none; //防止拖拉也防止到時候點擊的選取效果
    }
    .arrow {
        margin: 0 20px;
        font-size: 40px;
        color: gray;
        cursor: pointer;
        &:hover {
            color: var(--primary-color);
        }
    }
}

這樣好了以後應該就有上面彈跳視窗的樣子了,再來就要處理特效部分,這邊大家都可以繼續優化hotelPage 彈跳視窗UI設計或是加上特效。

hotelPage 左右滑banner特效


上面有提到簡單原理,所以第一個我們可以先處理開關彈跳視窗的部分,一樣使用useState配搭boolean的true和false

const [openSlider, setOpenSlider] = useState(false);

跟我們有稍微提到,點擊時,換成該照片的index以顯示,跟左右點擊滑動,所以我們也必須紀錄Slider的index為主要目地,所以一樣使用useState紀錄

const [sliderIndex, setSiderIndex] = useState(0);

預設為0就第一張照片的意思,所以大致上ok後,原本開啟彈跳視窗的方式是使用onClick是搭配(!onSlider)來改動開與關,如之前做過的calendar方式

onClick={() => setOpenCalendar(!openCalendar)}

但這邊我們不這樣做,因為我們要更進階在點擊的時候,再加入新的傳入變數slider的index,因為要同時做兩件事,開啟與傳入index變數
所以這邊設置了一個新的函數clickSilde把他們都包起來,如圖

const clickSlider = (index) => {
setOpenSlider(true);
setSiderIndex(index);
}
onClick={() => clickSlider(i)}

這邊不使用setOpenSlider(!openSlider);是因為開關處不是同一個,打開是點擊照片,但關起是按視窗的關閉,所以用不到!轉換boolean
接下來我們將先完整開關視窗,所以將整個slider先用{ && } 一樣如同calendar的方式,忘記可以去複習,將整個div包裹起來

那因為應該是useState初始值是false的所以等於隱藏整個slider,然後可以點點看照片,看有沒有跑出視窗,並小心不要點到popupcomment的觸控區域上應該是都可以打開slider視窗,或是也可以一樣onClick放入popupcomment上讓他點擊也有反應都可以,並再將原本div.wrapperBody裡面的{photos[0].src}換成{photos[sliderIndex].src}讓他可以點擊照片打開就是這張照片,還有點擊關閉或是icon X 可以關閉視窗

onClick={()=>setOpenSlider(false)}

完成後要開始處理點右鍵與點左鍵視窗移動照片的函數,所以一樣我們要有觸發左鍵右鍵的函數,首先這個函數會紀錄新的index,所以我宣告newSliderInde與紀錄lastPicutre是照片array資料個數-1的原因是有兩種情況,當今天往右點擊到最後一張照片了理當來說就不能往下了,要跳回最後一張,所以今天往左點擊到第一張照片也是,必須跳到第一張,讓照片可以是無限點擊的迴圈

const slideDirection = (direction) => {
let newSliderIndex;
let lastPicutre  = photos.length - 1
if (direction == "left") {
  sliderIndex == 0 ? newSliderIndex = lastPicutre  : newSliderIndex = sliderIndex - 1
  setSiderIndex(newSliderIndex) 
} else {
  sliderIndex == lastPicutre  ? newSliderIndex = 0 : newSliderIndex = sliderIndex + 1
  setSiderIndex(newSliderIndex)
}
}

所以這邊使用了兩個 ? : (if esle) 第一個是當今天點擊的是左鍵direction == "left",那如果是這種sliderIndex == 0情況,代表點到第一張照片了必須跳回最後一張,所以回傳新的index 為lastPicutre ,newSliderIndex = lastPicutre,如果不是就繼續減index並最後紀錄進去,如下圖

所以完成的div應該長這樣

{ openSlider &&
<div className="slider">
    <div className="sliderWrapper">
      <div className="wrapperTitle">
      <div className='TitleName'>台南微醺文旅</div>
      <span className="CloseSign" onClick={()=>setOpenSlider(false)}>關閉
        <FontAwesomeIcon icon={faXmark}   /></span> 
      </div>
      <div className="wrapperBody">
        <FontAwesomeIcon icon={faAngleLeft} className="arrow" onClick={()=>slideDirection("left")} />
        <img src={photos[sliderIndex].src} alt="" />
        <FontAwesomeIcon icon={faAngleRight} className="arrow" onClick={()=>slideDirection("right")}/>
      </div>
    </div>
  </div>
  }    

恭喜你這邊就差不多完成了,最後的最後我們來做個login/register做結尾

login/register UI結尾

這邊就不特別講解了,因為相信太簡單不太需要講解XD
附上完成圖

但這邊值得講的是navbar的改動會比較大,因為跟之前做的navbar在註冊與登入這邊很明顯的不用登入與註冊按鈕這些,所以一樣要用component傳props的方式來做,就跟那時候在做HomePage在做

<Announcement type={"Upper half"}/>

時一樣,忘記的話可以去前面複習,我們要做一個專屬login與auth的navbar,首先先看減少那些部分

所以需要讓navbar一樣有兩種呈現的方式,一個是原本的,一個是type=auth的形式,專為login與register的簡化版navbar

  <Navbar type={"auth"}/>


所以loginPage與registerPage導入Navbar時要輸入上面這一樣,用props導入來區分

最後一步是login與register的大量link連接,包括了app.jsx的route與navbar的註冊登入按鈕,跟loginPage與RegisterPage的各自"註冊&創建一個張號"與"已有張號?按這裡登入"等連結,附上各自的連結

loginPage.div
loginPage.scss
registerPage.div
registerPage.scss
這邊我都將這個login/register放在page頁面,屬於分頁的一種
整體的UI完整版github連結
challenge.day11.version

結論

今天終於結束了長達11天的UI網站版面設計,並開始學了一些網站炫技的部分,這部分也非常實用很適合深入研究加入各種motion與套件,然後玩出一套最吸睛的版面,彈跳式視窗、hoverIn還是click特效,都是讓你的網站與別人與眾不同的開始,明天將會進入新篇章nodejs的Api連接,要來介紹如何使用mongoDB與後端連接,將會網站是否能連動最重要的課題!


上一篇
「全端挑戰」了解Css Grid介紹與應用,/:id 與params產品id分頁
下一篇
「全端挑戰」node.js Api介紹與實作、async function 與try{} catch介紹
系列文
自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言