青いやつの進捗日記。

べんきょうのしんちょくをかいていきます。

タブ切り替え等で中身のコンテンツの高さが変わる、display:noneで切り替えているときの、その切り替えているコンテンツの下の位置でスクロール位置保持

こんな感じでタブで選んで中身のコンテンツを入れ替えたい。

わかりづらいがこの上がタブで、下がコンテンツ。 f:id:XxGodmoonxX:20191120012255p:plain

しかも、そのタブは上下にあり、どちらでも中身のコンテンツを入れ替えられる。

↓下 f:id:XxGodmoonxX:20191120012320p:plain

つまり、 f:id:XxGodmoonxX:20191120012323j:plain

こういう感じ。

で、問題なのは、

・このコンテンツは1つ目だけが高さが少ない ・このコンテンツをdisplay: blockdisplay:noneで入れ替えている

の2つ。

高さが変わらないのであればdisplayで切り替えても問題ない。

もしくは高さが変わるならばdisplayではなくopacityとvisibilityで消して、コンテンツ自体は一番大きいのは普通に置いてそれ以外をabsoluteで重ねておいておけば良い。が、それだと下のタブからコンテンツまでのマージンがバラバラになってしまう。

であるならばdisplayでコンテンツの存在ごと切り替えてマージンは統一するしかない。

なのでこの方針しかないのだが、これだと問題がある。

まず、上のタブで切り替えるのならばスクロール位置は固定でも問題ない。 が、下のタブで切り替えるのであれば問題がある。

上の図の通りだが、1つ目のタブで現れるコンテンツは高さが小さく、2つ目のタブで現れるコンテンツの高さが大きいと、1つ目→2つ目とタブを切り替えたとき、同じスクロール位置の値だと、2つ目のときはコンテンツの真ん中くらいになってしまう。

(ちなみになぜかChromeでは良い感じに補完されて、下のタブをクリックしたときの見え方のままになる。本当はChromeでも位置ずれるらしいが…?)

で、そんなときの対策方法。

    var $tabItems = $(".voice__tabItem");
    
    $tabItems.on("click", function() {
    
      // 現時点で選ばれている箱の高さ
      var beforeHeight = $(".voice__box.is-selected").height();
    
      // ここからタブクリックでコンテンツ入れ替える記述
    
      // タブをクリックしたらその何番目のタブか
      var $tab = $(this).parent();
      var $item = $tab.find(".voice__tabItem");
      var index = $item.index($(this));
    
      // タブをクリックしたらそのタブにクラス付与、上のタブも下のタブも
      $tabItems.removeClass("is-selected");
      $(".voice__tab--top .voice__tabItem")
        .eq(index)
        .addClass("is-selected");
      $(".voice__tab--bottom .voice__tabItem")
        .eq(index)
        .addClass("is-selected");
      // タブの順番と同じボックスにクラス付与
      $(".voice__box").removeClass("is-selected");
      $(".voice__box")
        .eq(index)
        .addClass("is-selected");
    
      // ここまでタブクリックでコンテンツ入れ替える記述
    
      // タブ押した後に選ばれた箱の高さ
      var afterHeight = $(".voice__box.is-selected").height();
    
      // 前に選ばれていた箱と選ばれた箱の高さの差
      var changeHeight = afterHeight - beforeHeight;
    
      // Safariでタブクリック時にスクロール位置がずれてしまう(macOSとiOSどちらも)
      // Windowsで見たらIEもEdgeもだめでした
      // 箱の高さが変わるが、その際にwindowの一番上からのスクロールトップの値がずれてしまうため。
      // おそらくChromeは良い感じに補完してくれている
      // Chrome以外の時を判定する記述
      if (!_browser.chrome) { // ←ここはなんかChromeの判定をうまく書いてください
        // クリックしたタブの親がクラスにvoice__tab--bottomを持っていたら
            // 上のタブはvoice__tab--topとクラスふってる、
            // 上のタブならスクロール位置補完してあげる必要がないので
        if (
          $(this)
            .parent()
            .hasClass("voice__tab--bottom")
        ) {
          // タブクリックして箱が新しくなった時のスクロール位置を保存
          var scrtop = $(window).scrollTop();
          // 上記の上記のスクロール位置から箱の変化分を足す、かかる時間は0秒
          $("html,body").animate(
            { scrollTop: scrtop + changeHeight },
            { duration: 0 }
          );
        }
      }
    });

まずタブクリックで中身のコンテンツを入れ替える記述が書いてある。

で、クリックして中身のコンテンツを入れ替える前と後で、それぞれのコンテンツの高さを取得しておく。beforeHeightAfterHeight

その前後の値の差をとる。changeHeight

で、中身のコンテンツを入れ替えた後のスクロール位置をとる。scrTop

で、jQueryのanimateで、 コンテンツ入れ替えた後のスクロール位置scrTop + コンテンツ入れ替えた前後の高さの差changeHeightを足して動かしてあげる。

例として、

上記の図で1つ目のタブで現れるコンテンツの高さが1200、2つ目のタブで現れるコンテンツの高さが2000とします。 beforeHeightは1200、afterHeightは2000。changeHeightは800。

1つ目のタブを選択した状態で下部のタブの2つ目のタブを押します。 下部のタブを押す前のスクロール位置を3000とすると、動かないので2つ目のタブのコンテンツに入れ替わってもスクロール位置は3000です。scrTopは3000。

で、このままだと上記の図の通り、スクロール位置が3000のままだと2つ目のコンテンツを表示している状態では下部のタブをクリックできる位置になくコンテンツ中のところにスクロール位置3000があります。

なので、同じ見た目にするためにはこのずれた高さchangeHeight分ずらします。なので3000 + 800 = 3800。

1つ目のコンテンツが表示されている状態で下部のタブを押すときの画面上の見た目のスクロール位置(今回は3000)と同じ見た目の状態を維持して2つ目のコンテンツに入れ替えるためにはスクロール位置を瞬時に3800にする必要がある、ということでした。