• トップ
  • ブログ一覧
  • 【Next.js】React SlickのslidesToShowが少数でも、高さを動的にしたい
  • 【Next.js】React SlickのslidesToShowが少数でも、高さを動的にしたい

    りゅうちゃん(エンジニア)りゅうちゃん(エンジニア)
    2024.01.12

    IT技術

    今回はNextjsでReact Slickを改造していく記事です。

    利用したバージョン

    "react-slick": "^0.29.0"

    React Slickとは

    横スライドして表示を切り替えるやつです。

    以下のコードから、

    1'use client'
    2
    3import React from "react"
    4import Slider from "react-slick" // https://react-slick.neostack.com/
    5
    6export default function Page() {
    7  const settings = {
    8    dots: true,
    9    infinite: false,
    10    speed: 500,
    11    slidesToShow: 1,
    12    slidesToScroll: 1,
    13  }
    14
    15  return (
    16    <>
    17      <div className="bg-slate-600 h-10" />
    18      <Slider ref={sliderRef} className="slick-list-overflow" {...settings}>
    19        <div className="bg-lime-200 h-20"/>
    20        <div className="bg-lime-400 h-28"/>
    21        <div className="bg-lime-600 h-36"/>
    22        <div className="bg-lime-800 h-44"/>
    23      </Slider>
    24      <div className="bg-slate-400 h-10" />
    25    </>
    26  )
    27}

    こんな感じの画面ができます。



    全体像

    最終的には以下のような画面を目指します。



    adaptiveHeightを有効にする

    実はSlick自身には高さを動的にしてくれる機能があり、それがadaptiveHeightです。
    Sliderで現在表示している要素によって、Slider自身の高さを自動調整してくれるようになります。

    1const settings = {
    2  adaptiveHeight: true, // 追加
    3}


    slidesToShowに少数を指定する

    slidesToShowは、1つの画面上に表示する要素の数を操作できる引数です。
    なのでslidesToShowを1.5などの少数に変更した場合、Sliderの次の要素を少しだけ表示することが可能です。

    1const settings = {
    2  slidesToShow: 1.5,  // 値を変更
    3  adaptiveHeight: false, // 値を変更
    4}

    しかし最後の要素を表示した時、以下のように高さが0になってしまったり、adaptiveHeightを有効にした場合に次の要素が不自然に隠れてしまいます。


    ここをいい感じに表示できるよう改良していきましょう!

    (slidesToShowがドキュメント上ではType: intってなってるので、少数だと動かなくて当然だろ!ってツッコミはナシで…)

    原因

    最後の要素を表示した時に高さが0になってしまう問題を探っていきましょう。

    beforeChangeを利用して、スライド時のIndexを見ていきます。

    1const settings = {
    2  beforeChange: (old: number, next: number) => {
    3    console.log(`oldIndex: ${old}`)
    4    console.log(`nextIndex: ${next}`)
    5  }
    6}

    最後のIndexだけ少数になっており、正常にSlider内の要素の配列から、取得できていないようです。
    ちなみに、要素の数を増やしても最後のIndexだけ少数になり、本来のIndexからslidesToShowの少数部分がマイナスされた値になっているようです。

    Indexをいい感じに補正した上で、Sliderの高さ調整を行なっていきましょう!

    修正

    Indexを補正する

    こちらは単純に、Math.ceilを使って少数を切り上げて整数にしてあげるだけです。

    1const [currentSlide, setCurrentSlide] = useState(0)
    2
    3const settings = {
    4  beforeChange: (_: number, next: number) => {
    5    setCurrentSlide(Math.ceil(next))
    6  }, // 追加
    7}

    Sliderの高さ調整を行う

    まずはSliderの高さを変更した際、親からはみ出した箇所の表示を調整するcssを定義します。

    1@import "slick-carousel/slick/slick.css"; 
    2@import "slick-carousel/slick/slick-theme.css";
    3
    4.slick-slider.slick-list-overflow .slick-list {
    5    overflow-x: clip !important;
    6    overflow-y: visible !important;
    7    z-index: 10;
    8}

    次にPage側を変更しましょう。
    まずはSliderにrefと、前に追加したcssを適用させます。

    1const sliderRef = useRef(null)
    2
    3// adaptiveHeightを削除し、高さ調整は後述するhooksを利用
    4const settings = {
    5  dots: true,
    6  infinite: false,
    7  speed: 500,
    8  slidesToShow: 1.5,
    9  slidesToScroll: 1,
    10  beforeChange: (_: number, next: number) => {
    11    setCurrentSlide(Math.ceil(next))
    12  },
    13}
    14
    15return (
    16  <>
    17    <div className="bg-slate-600 h-10" />
    18    <div>
    19      <Slider ref={sliderRef} className="slick-list-overflow" {...settings}>
    20        <div className="bg-lime-200 h-20"/>
    21        <div className="bg-lime-400 h-28"/>
    22        <div className="bg-lime-600 h-36"/>
    23        <div className="bg-lime-800 h-44"/>
    24      </Slider>
    25    </div>
    26    <div className="bg-slate-400 h-10" />
    27  </>
    28)

    次にhooksを使って、Sliderの現在表示しているIndexが変更された場合に、refから取得したSliderの高さを変更します。

    1// react-slick側で設定される、現在表示中のスライドタグに設定されるクラス
    2const CURRENT_SLIDE_CLASS = '.slick-current'
    3
    4useEffect(() => {
    5  if (!sliderRef.current?.innerSlider?.list) { return }
    6
    7  // スライダーの高さを調整
    8  const currentSlideElement = sliderRef.current.innerSlider.list.querySelector(CURRENT_SLIDE_CLASS)
    9  if (currentSlideElement) {
    10    sliderRef.current.innerSlider.list.style.height = `${currentSlideElement.clientHeight}px`
    11    // Sliderの親要素にHeightを追加する
    12    if (sliderRef.current.innerSlider.list.parentElement?.parentElement) {
    13      sliderRef.current.innerSlider.list.parentElement.parentElement.style.height = `${currentSlideElement.clientHeight}px`
    14    }
    15  }
    16}, [currentSlide])

    ちなみにChromeのDevツールなどで確認すると、以下のような構成になっているので、参考にしてみてください。

    1<div class="slick-slider slick-list-overflow">
    2  <div class="slick-list">
    3     <div class="slick-track">
    4         <div class="slick-slide" data-index="0" /> // Sliderのchildrenがそれぞれに入る
    5         <div class="slick-slide" data-index="1" />
    6         <div class="slick-slide" data-index="2" />
    7         <div class="slick-slide" data-index="3" />
    8          ...

    上記を実装した場合、以下のような表示になります。



    他のタグと干渉しないような調整

    ここからは好みですが、Sliderのドット表示や中身を他の要素に干渉させたくない場合などで以下を追加してみてください。

    1if (currentSlideElement) {
    2  const dotsHeight = 24
    3  sliderRef.current.innerSlider.list.style.height = `${currentSlideElement.clientHeight}px`
    4  // Sliderの親要素にHeightを追加する
    5  if (sliderRef.current.innerSlider.list.parentElement?.parentElement) {
    6    sliderRef.current.innerSlider.list.parentElement.parentElement.style.height = `${
    7      currentSlideElement.clientHeight + dotsHeight
    8    }px`
    9  }
    10}

    ※Sliderのslick-dotsがposition: absoluteを持つため、Sliderをdivで囲っていますが、ドット自体が不要な場合はdivで囲わなくても同じような挙動になります。



    まとめ

    最終的なコードは以下になります。

    1'use client'
    2
    3import React, { useEffect, useRef, useState } from "react"
    4import Slider from "react-slick"
    5
    6export default function Page() {
    7  const sliderRef = useRef<Slider | null>(null)
    8  const [currentSlide, setCurrentSlide] = useState(0)
    9
    10  // react-slick側で設定される、現在表示中のスライドタグに設定されるクラス
    11  const CURRENT_SLIDE_CLASS = '.slick-current'
    12
    13  useEffect(() => {
    14    if (!sliderRef.current?.innerSlider?.list) { return }
    15
    16    // スライダーの高さを調整
    17    const currentSlideElement =
    18      sliderRef.current.innerSlider.list.querySelector(CURRENT_SLIDE_CLASS)
    19
    20    if (currentSlideElement) {
    21      const dotsHeight = 24
    22      sliderRef.current.innerSlider.list.style.height = `${currentSlideElement.clientHeight}px`
    23      // Sliderの親要素にHeightを追加する
    24      if (sliderRef.current.innerSlider.list.parentElement?.parentElement) {
    25        sliderRef.current.innerSlider.list.parentElement.parentElement.style.height = `${
    26          currentSlideElement.clientHeight + dotsHeight
    27        }px`
    28      }
    29    }
    30  }, [currentSlide])
    31
    32  const settings = {
    33    dots: true,
    34    infinite: false,
    35    speed: 500,
    36    slidesToShow: 1.5,
    37    slidesToScroll: 1,
    38    beforeChange: (_: number, next: number) => {
    39      setCurrentSlide(Math.ceil(next))
    40    },
    41  }
    42
    43  return (
    44    <>
    45      <div className="bg-slate-600 h-10" />
    46      <div className="overflow-hidden">
    47        <Slider ref={sliderRef} className="relative slick-list-overflow" {...settings}>
    48          <div className="bg-lime-200 h-20"/>
    49          <div className="bg-lime-400 h-28"/>
    50          <div className="bg-lime-600 h-36"/>
    51          <div className="bg-lime-800 h-44"/>
    52        </Slider>
    53      </div>
    54      <div className='bg-slate-400 h-10' />
    55    </>
    56  )
    57}
    りゅうちゃん(エンジニア)

    りゅうちゃん(エンジニア)

    おすすめ記事