整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          CSS設置網頁在Safari快速滾動和溫柔的回彈

          CSS設置網頁在Safari快速滾動和溫柔的回彈

          實現這個效果很簡單,只需要加一行css代碼即可:

          -webkit-overflow-scrolling : touch;

          可用以下網頁測試:

          <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
              <head>
                  <meta charset="utf-8" />
                  <title>scroll</title>
                  <style type="text/css">
           .container {
           width : 300px;
           height : 50%;
           -webkit-box-sizing : border-box;
           position : relative;
           overflow-y : auto;
           background-color : cyan;
           -webkit-overflow-scrolling : touch;  /* liuhx:可以把這整行注釋掉對比差別 */
           }
           ul {
           height: 50px;
           }
                  </style>
              </head>
          <body>
              <div align="center">
                  <nav class="container">
           <ul>1</ul>
           <ul>2</ul>
           <ul>3</ul>
           <ul>4</ul>
           <ul>5</ul>
           <ul>6</ul>
           <ul>7</ul>
           <ul>8</ul>
           <ul>9</ul>
           <ul>10</ul>
           <ul>11</ul>
           <ul>12</ul>
           <ul>13</ul>
           <ul>14</ul>
           <ul>15</ul>
           <ul>16</ul>
           <ul>17</ul>
           <ul>18</ul>
           <ul>19</ul>
           <ul>20</ul>
                  </nav>
              </div>
          </body>
          </html>

          評論

          不久看到這樣一個很有趣的效果,它的滾動條是沿著圓角邊緣滾動的,效果如下

          你可以查看原鏈接來體驗一下

          https://codepen.io/jh3y/pen/gOEgxbd

          這是如何實現的呢?

          原效果中由于為了兼容不支持CSS滾動驅動的瀏覽器,特意用 JS做了兼容,所以看著比較復雜,其實核心非常簡單,下面我將用最簡短的 CSS 來復刻這一效果,一起看看吧

          一、SVG 路徑動畫

          從本質上來講,其實是一個 SVG 路徑動畫。

          具體如何實現呢?

          首先,我們通過設計軟件繪制一個這樣的路徑

          注意設置描邊的大小還有端點的類型,比如下面是round效果

          然后導出SVG,可以得到這樣一段代碼

          <svg viewBox="0 0 31 433" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M4 4C9.96737 4 15.6903 6.37053 19.9099 10.5901C24.1295 14.8097 26.5 20.5326 26.5 26.5V406.5C26.5 412.467 24.1295 418.19 19.9099 422.41C15.6903 426.629 9.96737 429 4 429" stroke="black" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
          

          然后,如何讓這段SVG動起來呢?

          很簡單,現在SVG是一段實線,我們可以通過stroke-dasharray設置成虛線,比如

          path{
            stroke-dasharray: 80
          }
          

          這樣會得到一個實線和虛線間隔都是80的虛線

          如果希望虛線空白的地方更大一點,該怎么設置呢?很簡單,繼續往后加

          path{
            stroke-dasharray: 80 120
          }
          

          效果如下

          所以,這種寫法其實相當于把當前的值無限重復,示意如下

          當然,我們這里不需要設置的這么復雜,只需要一小段實線就夠了,所以是實現加上一段足夠長的虛線(超過路徑本身就行),實現如下

          path{
            stroke-dasharray: 80 1000
          }
          

          這樣就得到了一小段實線

          那么,如何讓他動起來呢?很簡單,改變一下偏移就可以,這個可以用stroke-dashoffset來實現

          比如,我們

          @keyframes scroll {
            to {
              stroke-dashoffset: -370
          	}
          }
          path{
            stroke-dasharray: 80 1000;
            animation: scroll 3s alternate-reverse infinite;
          }
          

          效果如下

          是不是有點像呢?

          我們再調整一下起始偏移量,讓它出去一點

          @keyframes scroll {
            0% { stroke-dashoffset: 75; }
            100% { stroke-dashoffset: -445; }
          }
          

          這樣就更接近我們想要的效果了

          整個運動原理就是這樣了,接著往下看

          二、CSS 滾動驅動動畫

          接下來需要通過滾動驅動動畫將容器滾動與CSS動畫「聯動」起來。

          關于CSS 滾動驅動可以參考我之前寫的這篇文章:CSS 滾動驅動動畫終于正式支持了~

          簡單來講,「CSS 滾動驅動動畫」指的是將「動畫的執行過程由頁面滾動」進行接管,也就是這種情況下,「動畫只會跟隨頁面滾動的變化而變化」,也就是滾動多少,動畫就執行多少,「時間不再起作用」

          先簡單布局一下

          <div class="list">
            <div class="item" id="item_1">1</div>
            <div class="item" id="item_2">2</div>
            <div class="item" id="item_3">3</div>
            <div class="item" id="item_4">4</div>
            <div class="item" id="item_5">5</div>
            <div class="item" id="item_6">6</div>
            <div class="item" id="item_7">7</div>
          </div>
          

          美化一下

          然后,我們將默認的滾動條隱藏,用我們這個 SVG路徑來代替,由于需要絕對定位,我們再套一層父級

          <div class="wrap">
            <div class="list">
              <div class="item" id="item_1">1</div>
              <div class="item" id="item_2">2</div>
              <div class="item" id="item_3">3</div>
              <div class="item" id="item_4">4</div>
              <div class="item" id="item_5">5</div>
              <div class="item" id="item_6">6</div>
              <div class="item" id="item_7">7</div>
              <!--滾動條-->
              <svg class="scroller" viewBox="0 0 31 433" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path class="scroller_thumb" d="M4 4C9.96737 4 15.6903 6.37053 19.9099 10.5901C24.1295 14.8097 26.5 20.5326 26.5 26.5V406.5C26.5 412.467 24.1295 418.19 19.9099 422.41C15.6903 426.629 9.96737 429 4 429" stroke="black" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
              </svg>
            </div>
          </div>
          

          相關CSS如下

          .wrap{
            position: relative;
          }
          .scroller {
            position: absolute;
            top: 0;
            bottom: 0;
            right: 0;
            pointer-events: none;
            height: -webkit-fill-available;
            margin: 5px;
          }
          .scroller_thumb{
            stroke: hsl(0 0% 100% / 0.5);
            stroke-dasharray: 80 450;
            stroke-width: 8px;
            animation: scroll both 5s linear;
          }
          

          這樣結構就搭好了,只是滾動條會自動播放

          接下來就是最關鍵的一步,加上滾動驅動動畫

          .scroller_thumb{
            animation: scroll both 5s linear;
            animation-timeline: scroll();
          }
          

          但是這樣是不起作用的,直接使用scroll()會自動尋找它的相對父級,也就是.wrap,但實際滾動的其實是.list,所以這種情況下我們需要具名的滾動時間線,實現如下

          .list{
            scroll-timeline: --scroller;
          }
          .scroller_thumb{
            animation: scroll both 5s linear;
            animation-timeline: --scroller;
          }
          

          這樣SVG路徑動畫就能跟隨容器滾動而運動了

          三、CSS 滾動吸附

          原效果中還有一個滾動回彈的效果,當滾動到容器邊緣時,會自動回彈到起始位置。

          其實只需要用到 CSS scroll snap 就可以了

          https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-snap-type

          實現很簡單,給滾動容器添加scroll-snap-type屬性,表示這是個允許滾動吸附的容器

          .list{
            scroll-snap-type: y mandatory;
          }
          

          然后就指定需要吸附的點了,由于需要回彈的效果,所以滾動容器的首尾需要一個空白的容器,這里直接用兩個偽元素來生成

          .list::before,
          .list::after{
            content: '';
            height: 50px;
            flex-shrink: 0;
          }
          

          效果如下

          然后我們設置滾動吸附點就行了,設置第一個元素頂部和最后一個元素底部,其他元素居中就行了

          .item{
            scroll-snap-align: center;
          }
          .item:first-child{
            scroll-snap-align: start;
          }
          /*最后一個元素是 SVG,所以這里用倒數第二個元素*/
          .item:nth-last-child(2){
            scroll-snap-align: end;
          }
          

          這樣就實現了文章開頭的效果了

          完整代碼可以查看以下鏈接(無任何 JS

          • CSS round scroll (juejin.cn)[1]
          • CSS round scroll (codepen.io)[2]

          四、總結一下

          總的來說,CSS滾動驅動在滾動交互上帶來了無限可能,很多以前必須借助 JS來實現的都可以輕易實現,下面總結一下

          1. 從本質上來講,右側的滾動條其實是一個 SVG 路徑動畫
          2. SVG路徑可以通過stroke-dasharray設置虛實間隔
          3. 當虛線間隔足夠長時,超過路徑本身,就能得到一小塊實線
          4. 通過改變stroke-dashoffset偏移能夠實現路徑描邊動畫
          5. 借助 CSS滾動驅動動畫可以將SVG路徑動畫跟隨容器滾動而運動
          6. 滾動回彈效果其實就是CSS scroll snap實現的

          作者:XboxYan

          來源:微信公眾號:前端偵探

          出處:https://mp.weixin.qq.com/s/GaakgWhXm6jpY4PfISNHZQ

          篇會深化View拖拽實例,利用Flutter Animation、插值器以及AnimatedBuilder教大家實現帶動畫的抽屜效果。先來看效果:



          通過構思,我們可以設想到實現抽屜的方式就是用Stack控件將兩個Widget疊加顯示,用GestureDetector監聽手勢滑動,動態移動頂層的Widget,當監聽到手勢結束的時候根據手勢滑動的距離動態將頂部Widget利用動畫效果滑動到結束位置即可。

          實現底部Widget

          class DownDrawerWidget extends StatelessWidget {
           @override
           Widget build(BuildContext context) {
           return Container(child: Center(child: Text("底部Widget",),),);
           }
          }
          

          這個Widget太簡單了,就不細說了。

          實現頂部Widget

          class UpDrawerWidget extends StatelessWidget {
           @override
           Widget build(BuildContext context) {
           return Container(child: Center(child: Text("頂部Widget",),),);
           }
          }
          

          實現方式和底部是一樣的。

          實現可以移動的容器

          上面兩個Widget都是單純用來顯示的Widget,因此繼承了StatelessWidget。接下來我們需要根據手勢動態移動頂部的Widget,因此需要繼承StatefulWidget。

          // 頂部Widget
          class HomePageWidget extends StatefulWidget {
           @override
           State<StatefulWidget> createState()=> HomePageState();
          }
          class HomePageState extends State<HomePageWidget>
           with SingleTickerProviderStateMixin {
           @override
           void initState() {...}
           @override
           void dispose() {...}
           @override
           Widget build(BuildContext context) {...}
           void _onViewDragDown(DragDownDetails callback) {...}
           void _onViewDrag(DragUpdateDetails callback) {...}
           void _onViewDragUp(DragEndDetails callback) {...}
          }
          

          初始化狀態initState()

          這個方法是在Widget初始化的時候系統的回調函數,我們需要在該函數中初始化動畫

          AnimationController controller;
          @override
          void initState() {
           // 初始化動畫控制器,這里限定動畫時常為200毫秒
           controller=new AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
           // vsync對象會綁定動畫的定時器到一個可視的widget,所以當widget不顯示時,動畫定時器將會暫停,當widget再次顯示時,動畫定時器重新恢復執行,這樣就可以避免動畫相關UI不在當前屏幕時消耗資源。
           // 當使用vsync: this的時候,State對象必須with SingleTickerProviderStateMixin或TickerProviderStateMixin;TickerProviderStateMixin適用于多AnimationController的情況。
           // 設置動畫曲線,就是動畫插值器
           // 通過這個鏈接可以了解更多差值器,https://docs.flutter.io/flutter/animation/Curves-class.html,我們這里使用帶回彈效果的bounceOut。
           CurvedAnimation curve=new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
           // 增加動畫監聽,當手勢結束的時候通過動態計算到達目標位置的距離實現動畫效果。curve.value為當前動畫的值,取值范圍0~1。
           curve.addListener(() {
           double animValue=curve.value;
           double offset=dragUpDownX - dragDownX;
           double toPosition;
           // 右滑
           if (offset > 0) {
           if (offset > maxDragX / 5) {
           // 打開
           toPosition=maxDragX;
           isOpenState=true;
           } else {
           if (isOpenState) {
           toPosition=maxDragX;
           isOpenState=true;
           } else {
           toPosition=0.0;
           isOpenState=false;
           }
           }
           } else {
           if (offset < (-maxDragX / 2.0)) {
           // 關
           toPosition=0.0;
           isOpenState=false;
           } else {
           if (isOpenState) {
           toPosition=maxDragX;
           isOpenState=true;
           } else {
           toPosition=0.0;
           isOpenState=false;
           }
           }
           }
           dragOffset=(toPosition - dragUpDownX) * animValue + dragUpDownX;
           // 刷新位置
           setState(() {});
           });
           }
          

          結束Widget dispose()

          當Widget不可用將被回收的時候,系統會回調dispose()方法,我們在這里回收動畫。

          @override
          void dispose() {
           controller.dispose();
          }
          

          記錄按下的位置

           double dragDownX=0.0;
           void _onViewDragDown(DragDownDetails callback) {
           dragDownX=callback.globalPosition.dx;
           }
          

          拖動的時候刷新View的位置

          /**
           * 最大可拖動位置
           */
           final double maxDragX=230.0;
           double dragOffset=0.0;
           void _onViewDrag(DragUpdateDetails callback) {
           double tmpOffset=callback.globalPosition.dx - dragDownX;
           if (tmpOffset < 0) {
           tmpOffset +=maxDragX;
           }
           // 邊緣檢測
           if (tmpOffset < 0) {
           tmpOffset=0.0;
           } else if (tmpOffset >=maxDragX) {
           tmpOffset=maxDragX;
           }
           // 刷新
           if (dragOffset !=tmpOffset) {
           dragOffset=tmpOffset;
           setState(() {});
           }
           }
          

          離手的時候記錄位置并執行動畫

           /**
           * 脫手時候的位置
           */
           double dragUpDownX=0.0;
           void _onViewDragUp(DragEndDetails callback) {
           dragUpDownX=dragOffset;
           // 執行動畫,每次都從第0幀開始執行
           controller.forward(from: 0.0);
           }
          

          支持移動的Widget

          @override
           Widget build(BuildContext context) {
           return Transform.translate(
           offset: Offset(dragOffset, 0.0),
           child: Container(
           child: GestureDetector(
           onHorizontalDragDown: _onViewDragDown,
           onVerticalDragDown: _onViewDragDown,
           onHorizontalDragUpdate: _onViewDrag,
           onVerticalDragUpdate: _onViewDrag,
           onHorizontalDragEnd: _onViewDragUp,
           onVerticalDragEnd: _onViewDragUp,
           child: Container(
           child: new UpDrawerWidget(),
           ),),),);}
          

          Flutter動畫

          總結一下,想在Flutter中實現動畫,需要先創建一個AnimationController控制器;如果有特殊的插值要求,再創建一個插值器,調用controller.forward()方法執行動畫,通過addListener()的回調改變對應數值之后調用setState(() {})方法刷新位置即可。

          Flutter API還提供AnimatedBuilder用來簡化實現動畫的復雜性,讓我們不用手動調用addListener()方法。


          主站蜘蛛池模板: 视频一区二区三区人妻系列| 亚洲一区二区影视| 亚洲AV色香蕉一区二区| 日本精品一区二区三区在线视频| 日本精品3d动漫一区二区| 在线精品日韩一区二区三区| 男人免费视频一区二区在线观看 | 精品久久久久一区二区三区| 色系一区二区三区四区五区 | 一区二区三区视频免费观看| 久久蜜桃精品一区二区三区| 一区二区三区视频在线观看| 狠狠综合久久AV一区二区三区| 精品性影院一区二区三区内射| 成人在线一区二区| 久久久久人妻一区精品果冻| 久久精品无码一区二区三区日韩 | 精品一区二区三区高清免费观看| 国产精品一区二区久久精品无码| 亚洲爆乳无码一区二区三区| 亚洲AV无码一区二区乱子仑| 好吊视频一区二区三区| 搡老熟女老女人一区二区| 国产一区二区三区电影| 午夜福利一区二区三区高清视频| 无码国产精品一区二区高潮| 久久se精品一区二区国产| 又紧又大又爽精品一区二区| 无码国产精品一区二区免费模式| 制服美女视频一区| 精品不卡一区二区| 国精品无码一区二区三区左线 | 国产观看精品一区二区三区| 97se色综合一区二区二区| 中文字幕一区二区三区免费视频| 一本一道波多野结衣AV一区| 国产在线精品一区二区在线观看| 日本精品高清一区二区2021| 亚洲国产福利精品一区二区| 一区二区三区人妻无码| 日本人真淫视频一区二区三区|