我不確定這是怎麼回事。但是,這是一個故事。本文更多地是關於一個概念,該概念將幫助您以不同的方式思考動畫。碰巧的是,這個特定的示例具有無限的滾動 - 特別是無需複制任何一個卡片的“完美”無限滾動。
我為什麼在這裡?好吧,這一切都始於一條推文。一條推文讓我思考了佈局和側滾動內容。
我接受了這個概念,並在我的網站上使用了它。而且它在寫作時仍在行動。
然後,我開始考慮畫廊的觀點和側滾動概念。我們跳上了一個直播,決定嘗試製作類似舊的蘋果“覆蓋流”圖案之類的東西。還記得嗎?
我對此做出的第一個想法,假設我會做到這一點,這樣就可以在沒有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) 觸發器(滾動) }
我們在這裡做什麼?
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比元素更具動畫作用!在這裡,我們彎腰並操縱時間來創建一個幾乎完美的無限滑塊。沒有重複的元素,沒有混亂和良好的靈活性。
讓我們回顧一下我們涵蓋的內容:
您現在可以操縱時間! ?
進行“ meta” GSAP的概念開闢了各種可能性。你還能動畫嗎?聲音的?影片?至於“封面流”演示,這是去的地方!
以上是進行' meta gsap”:尋求'完美”無限滾動的詳細內容。更多資訊請關注PHP中文網其他相關文章!