首頁 >web前端 >css教學 >進行' meta gsap”:尋求'完美”無限滾動

進行' meta gsap”:尋求'完美”無限滾動

Joseph Gordon-Levitt
Joseph Gordon-Levitt原創
2025-03-25 10:23:10238瀏覽

進行“ meta gsap”:尋求“完美”無限滾動

我不確定這是怎麼回事。但是,這是一個故事。本文更多地是關於一個概念,該概念將幫助您以不同的方式思考動畫。碰巧的是,這個特定的示例具有無限的滾動 - 特別是無需複制任何一個卡片的“完美”無限滾動。

我為什麼在這裡?好吧,這一切都始於一條推文。一條推文讓我思考了佈局和側滾動內容。

我接受了這個概念,並在我的網站上使用了它。而且它在寫作時仍在行動。

然後,我開始考慮畫廊的觀點和側滾動概念。我們跳上了一個直播,決定嘗試製作類似舊的蘋果“覆蓋流”圖案之類的東西。還記得嗎?

我對此做出的第一個想法,假設我會做到這一點,這樣就可以在沒有JavaScript的情況下使用,就像上面的演示中一樣,它使用“漸進式增強”。我抓住了Greensock和Scrolltrigger,我們走了。我離開了這項工作,這讓我失望了。我有一些東西,但不能完全滾動滾動以操作我想要的方式。 “下一個”和“以前”按鈕不想打球。您可以在這裡看到它,並且需要水平滾動。

因此,我在Greensock論壇上打開了一個新線程。我幾乎不知道我要開放一些認真的學習!我們用按鈕解決了問題。但是,成為我,我不得不問是否可能還有其他事情。有沒有一種“乾淨”的方法來進行無限的滾動?我在溪流上嘗試了一些東西,但沒有運氣。我很好奇。我嘗試了這支筆在Scrolltrigger版本中創建的類似技術。

最初的答案是,要做有點棘手:

關於滾動上無限事物的困難部分是,滾動條是有限的,而您想要的效果不是。因此,您必須像此演示(在Scrolltrigger演示部分中找到)一樣循環滾動位置,或直接掛在與捲軸相關的導航事件(例如車輪事件)中,而不是實際使用實際的滾動位置。

我認為就是這種情況,很高興將其“原樣”。幾天過去了,當我開始挖掘它時,傑克(Jack)放棄了我的想法。現在,經過一大堆經過,我在這裡與您分享該技術。

動畫任何東西

GSAP通常會忽略的一件事是,您幾乎可以用它來動畫。這通常是因為視覺事物在思考動畫時會想到什麼 - 事物的實際身體運動。我們的第一個想法並不是要將該過程帶到元級別並從退後一步。

但是,考慮動畫的工作,然後將其分解為層。例如,您玩動畫片。卡通是一系列作品。每個構圖都是場景。然後,您有能力擦洗與遙控器的構圖集,無論是在YouTube上,使用電視遙控器還是其他任何內容。正在發生的事情幾乎有三個層次。

這是我們創建不同類型的無限循環所需的技巧。這是這裡的主要概念。我們用時間軸為時間表的播放頭部位置動畫。然後,我們可以用滾動位置擦洗該時間表。

不用擔心這聽起來令人困惑。我們將分解它。

去“元”

讓我們從一個示例開始。我們將創建一個補間,將一些盒子從左到右移動。這裡是。

十箱不斷向右到右。 Greensock非常簡單。在這裡,我們使用“從圖”和重複來保持動畫的運行。但是,我們在每次迭代的開始時都有差距。我們還在使用同路大車來擺脫運動,這將在我們繼續時發揮重要作用。

 gsap.fromto('。box',{
  XPercent:100
},{
  Xpercent:-200,
  交錯:0.5,
  持續時間:1,
  重複:-1,
  輕鬆:'無',
}))

現在是有趣的部分。讓我們暫停補間,並將其分配給變量。然後,讓我們創建一個播放它的補間。我們可以通過對補間的總時間進行補充來做到這一點,這使我們可以在考慮重複和重複延遲的同時獲得或設置Tween的播放頭。

 const shift = gsap.fromto('。box',{
  XPercent:100
},{
  暫停:是的,
  Xpercent:-200,
  交錯:0.5,
  持續時間:1,
  重複:-1,
  輕鬆:'無',
}))

固定持續時間= shift.duration()

gsap.to(shift,{
  總時間:持續時間,
  重複:-1,
  持續時間:持續時間,
  輕鬆:'無',
}))

這是我們的第一個“元”補間。它看起來完全相同,但我們正在添加另一個控制級別。我們可以在此層上更改內容而不會影響原始層。例如,我們可以將補間更改為power4.in。這完全改變了動畫,但不會影響基礎動畫。我們有點以後備為保護自己。

不僅如此,我們可能會選擇僅重複時間表的某個部分。我們可以與另一個從這樣做的事情來做到這一點:

代碼將是這樣的。

 gsap.fromto(shift,{
  總時間:2,
},{
  總時間:持續時間-1,
  重複:-1,
  持續時間:持續時間,
  輕鬆:'無'
}))

你看到這要去哪裡了嗎?看那個補間。儘管它一直在循環,但每次重複的數字都會翻轉。但是,盒子處於正確的位置。

實現“完美”循環

如果我們回到原始示例,則每個重複之間存在明顯的差距。

這是訣竅。解鎖一切的部分。我們需要建立一個完美的循環。

讓我們從重複三次轉移開始。它等於使用重複:3。請注意,我們如何刪除重複:-1。

 const getShift =()=> gsap.fromto('。box',{
  XPercent:100
},{
  Xpercent:-200,
  交錯:0.5,
  持續時間:1,
  輕鬆:'無',
}))

const loop = gsap.timeline()
  .add(getShift())
  .add(getShift())
  .add(getShift())

我們將最初的補間變成了返回補間的函數,然後將其添加到新的時間軸中。這給了我們以下內容。

好的。但是,仍然存在差距。現在,我們可以提出添加和定位這些二聚體的位置參數。我們希望它是無縫的。這意味著在前一個結束之前插入每組兩組。這是基於交錯和元素數量的價值。

常量性交錯= 0.5 //在我們的轉移之間使用
const box = gsap.utils.toarray('。box')
const loop = gsap.timeline({{
  重複:-1
}))
  .add(getShift(),0)
  .add(getShift(),boxes.length * stagger)
  .add(getShift(),boxes.length * stagger * 2)

如果我們更新時間表以重複並觀看(同時調整交錯以查看它如何影響事物)…

您會注意到中間有SA窗口會產生一個“無縫”循環。回想起我們操縱時間的早期技能嗎?這就是我們在這裡需要做的:循環循環“無縫”的時間窗口。

我們可以嘗試通過循環的那個窗口對總時間進行補充。

 const loop = gsap.timeline({{
  暫停:是的,
  重複:-1,
}))
.add(getShift(),0)
.add(getShift(),boxes.length * stagger)
.add(getShift(),boxes.length * stagger * 2)

gsap.fromto(loop,{
  總時間:4.75,
},,
{
  總時間:'= 5',
  持續時間:10,
  輕鬆:'無',
  重複:-1,
}))

在這裡,我們說的是總時間為4.75,並將周期的長度添加到此。一個週期的長度為5。這是時間軸的中間窗口。我們可以使用GSAP的nifty =做到這一點,這給了我們這一點:

花點時間消化那裡發生的事情。這可能是將您的頭纏住的最棘手的部分。我們正在計算時間表的時間窗口。很難可視化,但我已經走了。

這是一款手錶的演示,只有12秒鐘的手走一次。它是無限地循環的,重複:-1,然後我們使用從給定持續時間的特定時間窗口進行動畫。如果您減少說2和6​​的時間窗口,然後將持續時間更改為1,雙手重複時將從2點鐘到6點。但是,我們從未改變過基本動畫。

嘗試配置值以查看其如何影響事物。

在這一點上,最好為我們的窗戶位置建立一個公式。我們還可以在每個盒子過渡的持續時間內使用變量。

固定持續時間= 1
const cycle_duration = boxes.length *交錯
const start_time = cycle_duration(持續時間 * 0.5)
const end_time = start_time cycle_duration

我們可以在三倍的元素上循環循環,而不是使用三個堆疊的時間表,在那裡我們獲得了不需要計算位置的好處。不過,將其視為三個堆疊的時間表是一種巧妙的方式,可以理解這一概念,這是一種很好的方法。

讓我們更改我們的實現,從一開始就創建一個重要的時間表。

恆量交錯= 0.5
const box = gsap.utils.toarray('。box')

const loop = gsap.timeline({{
  暫停:是的,
  重複:-1,
}))

const shifts = [...盒子,...盒子,...盒子]

shifts.foreach((框,索引)=> {
  loop.fromto(box,{
    XPercent:100
  },{
    Xpercent:-200,
    持續時間:1,
    輕鬆:'無',
  },索引 *交錯)
}))

這更容易組合在一起,並為我們提供相同的窗口。但是,我們不需要考慮數學。現在,我們循環瀏覽三組盒子,並根據交錯定位每個動畫。

如果我們調整交錯,那看起來會怎樣?它會將盒子擠在一起。

但是,它破了窗口,因為現在總時間已經過去了。我們需要重新計算窗口。現在是插入我們之前計算出的公式的好時機。

固定持續時間= 1
const cycle_duration =交錯 * boxes.length
const start_time = cycle_duration(持續時間 * 0.5)
const end_time = start_time cycle_duration

gsap.fromto(loop,{
  總時間:start_time,
},,
{
  總時間:end_time,
  持續時間:10,
  輕鬆:'無',
  重複:-1,
}))

固定的!

如果我們想更改起始位置,我們甚至可以引入“偏移”。

恆量交錯= 0.5
const偏移= 5 *交錯
const start_time =(Cycle_duration(差異 * 0.5))偏移

現在,我們的窗口從不同的位置開始。

但是,這並不是很棒,因為它在兩端都給了我們這些尷尬的堆棧。為了擺脫這種效果,我們需要考慮一個框的“物理”窗口。或考慮他們如何進入和退出現場。

我們將使用Document.Body作為我們的示例的窗口。讓我們更新框,以作為單個時間表,盒子在Enter上和退出時向下擴展。我們可以使用Yoyo並重複:1進入進入和退出。

 shifts.foreach((框,索引)=> {
  const box_tl = gsap
    .timeline()
    .fromto(
      盒子,
      {
        Xpercent:100,
      },,
      {
        Xpercent:-200,
        持續時間:1,
        輕鬆:'無',
      },0
    )
    .fromto(
      盒子,
      {
        比例:0,
      },,
      {
        比例:1,
        重複:1,
        Yoyo:是的,
        輕鬆:'無',
        持續時間:0.5,
      },,
      0
    )
  loop.Add(box_tl,索引 *交錯)
}))

為什麼我們使用時間軸持續時間為1?它使事情更容易遵循。我們知道盒子位於中點時的時間為0.5。值得注意的是,放鬆將沒有我們通常在這裡想到的效果。實際上,放鬆實際上將在盒子的定位方式中發揮作用。例如,一個輕鬆的插件會在右邊的盒子上束縛,然後再移動。

上面的代碼為我們提供了。

幾乎。但是,我們的盒子在中間消失了一段時間。為了解決此問題,讓我們介紹即時屬性。它的作用類似於Animation-Fill模式:CSS中無。我們告訴GSAP,我們不想保留或預記錄任何盒子上的任何樣式。

 shifts.foreach((框,索引)=> {
  const box_tl = gsap
    .timeline()
    .fromto(
      盒子,
      {
        Xpercent:100,
      },,
      {
        Xpercent:-200,
        持續時間:1,
        輕鬆:'無',
        直系程序:false,
      },0
    )
    .fromto(
      盒子,
      {
        比例:0,
      },,
      {
        比例:1,
        重複:1,
        zindex:boxes.length 1,
        Yoyo:是的,
        輕鬆:'無',
        持續時間:0.5,
        直系程序:false,
      },,
      0
    )
  loop.Add(box_tl,索引 *交錯)
}))

那個小的變化為我們解決了!請注意我們還如何包括z index:boxes.length。這應該保護我們免受任何z指數問題。

我們有它!我們的第一個無縫無縫循環。沒有重複的元素和完美的延續。我們正在彎曲時間!如果您走了這麼遠,請輕拍自己的後背! ?

如果我們想一次看到更多的盒子,我們可以修補時機,交錯和輕鬆。在這裡,我們的交錯為0.2,我們還將不透明度引入了混合。

這裡的關鍵部分是我們可以利用retoperdelay,以便不透明度過渡比刻度更快。在0.25秒內褪色。等待0.5秒。淡出0.25秒以上。

 .fromto(
  盒子, {
    不透明度:0,
  },{
    不透明度:1,
    持續時間:0.25,
    重複:1,
    repoydelay:0.5,
    直系程序:false,
    輕鬆:'無',
    Yoyo:是的,
  },0)

涼爽的!我們可以對那些進出過渡的人做任何想做的事情。這裡的主要內容是,我們有時間窗口為我們提供無限的循環。

將其連接到滾動

現在我們有了一個無縫的循環,讓我們將其連接到滾動中。為此,我們可以使用GSAP的Scrolltrigger。這需要額外的補間來擦洗我們的循環窗口。請注意,我們現在也設置了現在要暫停的循環。

 const loop_head = gsap.fromto(loop,{
  總時間:start_time,
},,
{
  總時間:end_time,
  持續時間:10,
  輕鬆:'無',
  重複:-1,
  暫停:是的,
}))

const scrub = gsap.to(loop_head,{
  總時間:0,
  暫停:是的,
  持續時間:1,
  輕鬆:'無',
}))

這裡的訣竅是使用Scrolltrigger通過更新擦洗的總時間來擦洗循環的播放頭。我們可以通過多種方式來設置此滾動。我們可以水平或綁定到容器。但是,我們要做的就是將盒子包裹在.boxes元素中,然後將其固定在視口上。 (這可以修復其在視口上的位置。)我們還將堅持垂直滾動。檢查演示以查看.box的樣式,以將事物設置為視口的大小。

從'https://cdn.skypack.dev/gsap/scrolltrigger'導入scrolltrigger'
gsap.registerplugin(scrolltrigger)

scrolltrigger.create({{
  開始:0,
  結束:'= 2000',
  水平:錯誤,
  PIN:'.boxes',
  onupdate:self => {
    scrub.vars.totaltime = loop_head.duration() * self.progress
    scrub.invalidate()。 restart()
  }
}))

重要的部分是在update內部。那就是我們根據滾動進度設置補間的總時間。無效的呼叫將沖洗磨砂膏的任何內部記錄的位置。然後,重新啟動將位置設置為我們設置的新總時間。

嘗試一下!我們可以在時間軸中來回來回更新位置。

那有多酷?我們可以滾動以擦洗一個時間表,該時間表擦洗時間線,該時間表是時間表的窗口。消化一秒鐘,因為那是這裡發生的事情。

無限滾動時間旅行

到目前為止,我們一直在操縱時間。現在我們要去旅行!

為此,我們將使用其他一些GSAP實用程序,並且我們將不再擦洗LOOP_HEAD的總時間。相反,我們將通過代理進行更新。這是進行“ meta” GSAP的另一個很好的例子。

讓我們從標記播放頭位置的代理對像開始。

 const playhead = {位置:0}

現在,我們可以更新磨砂膏以更新位置。同時,我們可以使用GSAP的包裝實用程序,該實用程序將位置值包裹在Loop_head持續時間周圍。例如,如果持續時間為10,並且我們提供值11,我們將返回1。

 const position_wrap = gsap.utils.wrap(0,loop_head.duration())

const scrub = gsap.to(playhead,{
  位置:0,
  onupdate :()=> {
    loop_head.totaltime(position_wrap(playhead.position))
  },,
  暫停:是的,
  持續時間:1,
  輕鬆:'無',
}))

最後但並非最不重要的一點是,我們需要修改Scrolltrigger,以便更新磨砂膏上的正確變量。那是位置,而不是總時間。

 scrolltrigger.create({{
  開始:0,
  結束:'= 2000',
  水平:錯誤,
  PIN:'.boxes',
  onupdate:self => {
    scrub.vars.position = loop_head.duration() * self.progress
    scrub.invalidate()。 restart()
  }
}))

在這一點上,我們已經轉到了代理,我們看不到任何更改。

滾動時,我們想要無限的循環。我們的第一個想法可能是當我們完成滾動進度時滾動開始。這將做到這一點,向後滾動。儘管這就是我們想做的,但我們不希望播放頭向後擦洗。這是總時間到來的地方。還記得嗎?它根據總體策略獲得或設置播放頭的位置,其中包括任何重複和重複延遲。

例如,假設循環頭的持續時間為5,我們到達那裡,我們不會擦洗0。相反,我們將繼續將循環頭擦洗至10。如果我們繼續前進,它將達到15,等等。同時,我們將跟踪迭代變量,因為這告訴我們我們在磨砂膏中的位置。我們還將確保只有在達到進度閾值時才更新迭代。

讓我們從迭代變量開始:

讓迭代= 0

現在,讓我們更新我們的Scrolltrigger實現:

 const trigger = scrolltrigger.create({{
  開始:0,
  結束:'= 2000',
  水平:錯誤,
  PIN:'.boxes',
  onupdate:self => {
    const scroll = self.scroll()
    如果(滾動> self.end -end -1){
      //及時前進
      包裹(1,1)
    } else if(scroll <p>請注意,我們現在如何將迭代考慮到位置計算中。請記住,這與洗滌器包裹在一起。我們還檢測到何時達到滾動的極限,這就是我們包裝的重點。此功能設置適當的迭代值並設置新的滾動位置。</p><pre rel="JavaScript" data-line=""> const wrap =(iterationdelta,scrollto)=> {
  迭代= iterationdelta
  trigger.scroll(scrollto)
  trigger.update()
}

我們有無限的滾動!如果您有一隻帶有滾動輪的花哨的老鼠,您可以放鬆一下,那就去吧!很有趣!

這是一個顯示當前迭代和進度的演示:

滾動折斷

我們在那裡。但是,在使用這樣的功能時,總會有“很高興的人”。讓我們從滾動釦子開始。 GSAP使它變得容易,因為我們可以使用gsap.utils.snap而無需任何其他依賴。這樣可以處理捕捉到我們提供點的時間。我們聲明了0和1之間的步驟,並且在示例中有10個盒子。這意味著快照為0.1對我們有用。

 const snap = gsap.utils.snap(1 / boxes.length)

這返回了我們可以使用的函數來捕捉我們的位置值。

捲軸結束後,我們只想捕捉。為此,我們可以在Scrolltrigger上使用事件偵聽器。滾動結束時,我們將滾動到某個位置。

 scrolltrigger.addeventlistener('scrollend',()=> {
  捲軸(scrub.vars.position)
}))

這是捲軸的:

 const scrollToposition = posity => {
  const snap_pos = snap(位置)
  const進度=
    (snap_pos -loop_head.duration() *迭代) / loop_head.duration()
  const scroll = progressToscroll(progress)
  觸發器(滾動)
}

我們在這裡做什麼?

  1. 計算時間點以下點
  2. 計算當前進度。假設loop_head.duration()為1,我們已捕獲到2.5。這使我們的進度為0.5,導致迭代為2,其中2.5-1 * 2/1 ==== 0.5。我們計算進度,以便始終在1到0之間。
  3. 計算滾動目的地。這是我們的Scrolltrigger可以覆蓋的距離的一小部分。在我們的示例中,我們設定了2000年的距離,我們想要其中的一小部分。我們創建一個新的函數progressToscroll來計算它。
 const progressTosCroll = progress =>
  gsap.utils.clamp(1,trigger.end -1,gsap.utils.wrap(0,1,progress) * trigger.end)

此功能採用進度值並將其映射到最大的滾動距離。但是我們使用夾具來確保該值永遠不會為0或2000。這很重要。我們正在維護對這些價值觀的捕捉,因為這將使我們陷入無限的循環。

那裡有一點要接。查看此演示,該演示顯示了每個快照上的更新值。

為什麼事情變得更加挑剔?擦洗持續時間和輕鬆已改變。較小的持續時間和強力輕鬆使我們的快照。

 const scrub = gsap.to(playhead,{
  位置:0,
  onupdate :()=> {
    loop_head.totaltime(position_wrap(playhead.position))
  },,
  暫停:是的,
  持續時間:0.25,
  輕鬆:'power3',
}))

但是,如果您玩了該演示,您會注意到存在問題。有時,當我們在快照中包裹時,播放頭會跳動。我們需要通過確保在捕捉時包裝來解決這個問題 - 但是,只有在必要時才。

 const scrollToposition = posity => {
  const snap_pos = snap(位置)
  const進度=
    (snap_pos -loop_head.duration() *迭代) / loop_head.duration()
  const scroll = progressToscroll(progress)
  if(progress> = 1 ||進度<p>現在,我們有無限滾動的滾動!</p><h3>接下來是什麼?</h3><p>我們已經完成了實心無限滾動器的基礎。我們可以利用它來添加內容,例如控件或鍵盤功能。例如,這可能是一種連接“下一個”和“以前”按鈕和鍵盤控件的方法。我們要做的就是操縱時間,對嗎?</p><pre rel="JavaScript" data-line=""> const next =()=> scrollToposition(scrub.vars.position-(1 / boxes.length))
const prev =()=> scrollToposition(scrub.vars.position(1 / boxes.length))

//左右箭頭加A和D
document.addeventlistener('keydown',event => {
  if(event.keycode === 37 || event.keycode === 65)next()
  if(event.keycode === 39 || event.keycode === 68)prev()
}))

document.queryselector('。next')。 addeventListener('click'sext)
document.queryselector('。prev')。 addeventListener('click'prev)

那可以給我們這樣的東西。

我們可以利用我們的捲軸函數並根據需要顛簸價值。

就是這樣!

看到嗎? GSAP比元素更具動畫作用!在這裡,我們彎腰並操縱時間來創建一個幾乎完美的無限滑塊。沒有重複的元素,沒有混亂和良好的靈活性。

讓我們回顧一下我們涵蓋的內容:

  • 我們可以動畫動畫。 ?
  • 當我們操縱時間時,我們可以將時機作為定位工具。
  • 如何使用Scrolltrigger通過代理擦洗動畫。
  • 如何使用GSAP的一些很棒的實用程序來處理我們的邏輯。

您現在可以操縱時間! ?

進行“ meta” GSAP的概念開闢了各種可能性。你還能動畫嗎?聲音的?影片?至於“封面流”演示,這是去的地方!

以上是進行' meta gsap”:尋求'完美”無限滾動的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:共享元素過渡下一篇:共享元素過渡