我不确定这是怎么回事。但是,这是一个故事。本文更多地是关于一个概念,该概念将帮助您以不同的方式思考动画。碰巧的是,这个特定的示例具有无限的滚动 - 特别是无需复制任何一个卡片的“完美”无限滚动。
我为什么在这里?好吧,这一切都始于一条推文。一条推文让我思考了布局和侧滚动内容。
我接受了这个概念,并在我的网站上使用了它。而且它在写作时仍在行动。
然后,我开始考虑画廊的观点和侧滚动概念。我们跳上了一个直播,决定尝试制作类似旧的苹果“覆盖流”图案之类的东西。还记得吗?
我对此做出的第一个想法,假设我会做到这一点,这样就可以在没有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) 触发器(滚动) }
我们在这里做什么?
- 计算时间点以下点
- 计算当前进度。假设loop_head.duration()为1,我们已捕获到2.5。这使我们的进度为0.5,导致迭代为2,其中2.5-1 * 2/1 ==== 0.5。我们计算进度,以便始终在1到0之间。
- 计算滚动目的地。这是我们的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 id="接下来是什么">接下来是什么?</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中文网其他相关文章!

SVG具有自己的一套元素,属性和属性集,以至于内联SVG代码可能会变得漫长而复杂。通过利用CSS和SVG 2规范的一些即将到来的功能,我们可以减少该代码以进行清洁标记。

PWA(Progressive Web应用程序)已经与我们在一起了一段时间。但是,每次我尝试向客户解释它时,同样的问题都会出现:“我的用户会成为


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SublimeText3 英文版
推荐:为Win版本,支持代码提示!

SublimeText3汉化版
中文版,非常好用

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中