Slickによる「スライド式サムネイル付の画像スライダー」の実装例(asNavFor 未使用)
Slickを使用した「サムネイル付の画像スライダー」の実装として「オプション:asNavFor」を使用する方法が考えられるが、メインの画像スライダーに連動してサムネイル側もスライドされてしまう。
通常はこれでも問題はないが、サムネイルの数が表示件数以下でスライドはさせたくない場合でも適用されてしまう。
回避策として以下で紹介されているような方法もあるが、いずれもサムネイル側を完全固定しなければならない。
https://www.nowte.net/article/1330/
https://takblog.site/web/?p=144
そこで、「オプション:asNavFor」を使用しないで、メインの画像スライダーとサムネイルのスライダーを連動させる方法を考案。
これにより、以下のようなスライダーが実装可能になる。
- メインの画像スライダーが切り替わる毎に、サムネイル側もアクション
- サムネイルが既に表示領域内にある場合は、スライドさせずにアクティブ化のみ
- サムネイルが表示領域外の場合、表示件数分毎にスライドさせて、指定のサムネイルをアクティブ化
- サムネイルの数が表示件数以下の場合は完全固定
実装デモ
実例
https://hagiharaseizosho.shop/products/%E3%81%84%E8%8D%89%E3%83%A9%E3%82%B0
JavaScript(jQuery)
jQuery(document).ready(function($){
if($(“#imgViewer__main > *”).length > 1){
// メインスライダーに設定するSlickオプション
// ※予め変数化しておくと便利
// ※「asNavFor」を設定しないのがミソ
var mainConfig = {
fade: true,
autoplay: true,
speed: 1000,
infinite: true,
slidesToShow: 1,
swipe: true,
lazyLoad: ‘progressive’,
responsive: [
{
breakpoint: 992,
settings: {
arrows: false,
fade: false
}
}
]
};
// メインスライダーのアクション時(厳密にはアクション直前)に実行
// ※必ずSlick実装の前に書く
$(“#imgViewer__main”).on(“beforeChange”, function(event, slick, currentSlide, nextSlide){
// アクションを起こす毎に、サムネイルのアクティブ化切り替え
if(!$(“#imgViewer__thumbnail .carousel-item[data-slick-index='” + nextSlide + “‘]”).hasClass(“active”)){
$(“#imgViewer__thumbnail .carousel-item.active”).removeClass(“active”);
$(“#imgViewer__thumbnail .carousel-item[data-slick-index='” + nextSlide + “‘]”).addClass(“active”);
}
// サムネイルスライダーに設定されたSlickの各種オプション値を取得
var slickData = $(“#imgViewer__thumbnail”).get(0).slick;
// サムネイルスライダーに適用されている表示カラム数
var slidesToShow = slickData.options.slidesToShow;
// サムネイルスライダーのターゲットを決定(結果的に、条件一致の場合のみスライドアクションが発生)
// ターゲットのindex番号(初期値)※条件一致で、0以上になる
var nextToSlide = -1;
// メインスライダーで1番目(index:0)の画像が指定されている場合は、サムネイルスライダーの方も強制的に index:0 を指定する
if(nextSlide == 0){
nextToSlide = 0;
// それ以外の場合は条件指定
} else {
// メインスライダーで、表示中の画像が先頭ではなく、かつ「slick-next」がクリックされた場合
if(nextSlide > currentSlide && currentSlide > 0){
// サムネイル側で、表示領域分の先頭にあたる場合は、サムネイルのターゲットを指定
// (次のindex ÷ 表示カラム数 の余りがゼロ)
if(nextSlide % slidesToShow == 0){
nextToSlide = nextSlide;
}
// それ以外はさらに条件指定
} else {
// 表示中の画像が末尾にあたる (次のindex + 1 が、画像件数と一致)
// または、表示領域分の末尾にあたる場合は、サムネイルのターゲットを指定
// ( (次のindex + 1) ÷ 表示カラム数 の余りがゼロ )
if(nextSlide + 1 == slickData.slideCount || (nextSlide + 1 != slickData.slideCount && (nextSlide + 1) % slidesToShow == 0)) {
// サムネイル中のターゲットが含まれる表示領域分の先頭のindex番号を指定
nextToSlide = Math.floor(nextSlide / slidesToShow) * slidesToShow;
}
}
}
// 上記条件で、ターゲット指定がある場合
if(nextToSlide >= 0){
// ・・・で、かつスライド済でない場合のみ、サムネイルをスライドさせる
if(nextToSlide != slickData.currentSlide){
$(“#imgViewer__thumbnail”).slick(“slickGoTo”, nextToSlide, false);
}
}
});
// メインスライダーにslickを実装
$(“#imgViewer__main”).slick(mainConfig);
// サムネイルスライダーに設定するSlickオプション
// ※「asNavFor」を設定しないのがミソ
var subConfig = {
autoplay: false,
speed: 1000,
infinite: true,
appendArrows: ‘#imgViewer > .carousel-outline__thumbnail’,
slidesToShow: 6,
slidesToScroll: 6,
swipe: true,
lazyLoad: ‘progressive’,
responsive: [
{
breakpoint: 992,
settings: {
arrows: false,
slidesToShow: 4,
slidesToScroll: 4
}
},
{
breakpoint: 768,
settings: {
arrows: false,
slidesToShow: 3,
slidesToScroll: 3
}
}
]
};
// サムネイルスライダーにslickを実装
$(“#imgViewer__thumbnail”).slick(subConfig);
// サムネイルがクリックされた際に、メインスライダーを切り替える
$(“#imgViewer__thumbnail .carousel-item > .img-wrap”).on(“click”, function(){
var index = $(this).parent().attr(“data-slick-index”);
$(“#imgViewer__main”).slick(“slickGoTo”, index, false);
});
}
});
HTML
<div id=”imgViewer”>
<div class=”carousel-wrap__main”>
<div id=”imgViewer__main” class=”clearfix”>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
</div>
</div>
<div class=”carousel-outline__thumbnail”>
<div class=”carousel-wrap__thumbnail”>
<div id=”imgViewer__thumbnail” class=”clearfix”>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
<div class=”carousel-item”>
<div class=”img-wrap”>
<div class=”img”></div>
</div>
</div>
</div>
</div>
</div>
</div>
SCSS
#imgViewer {
.carousel {
&-wrap {
&__main {
position: relative;
overflow: hidden;
padding-top: 56.25%;
}
&__thumbnail {
position: relative;
overflow: hidden;
}
}
&-outline__thumbnail {
> .slick-arrow {
width: 40px;
height: 80px;
&.slick-prev { left: -47px; }
&.slick-next { right: -47px; }
&:before, &:after {
width: 20px;
}
}
}
&-item {
> .img-wrap {
> .img {
&:not(div) {
display: block;
}
position: relative;
overflow: hidden;
width: 100%;
height: 0;
padding-top: 56.25%;
font-size: 0.5em;
text-align: left;
text-indent: -9999px;
background-color: #ccc;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
}
}
}
.slick-arrow {
position: absolute;
top: 50%;
border: none;
background: none;
outline: 0;
width: 60px;
height: 90px;
font-size: 0.5em;
text-align: left;
text-indent: -9999px;
transform: translateY(-50%);
z-index: 10;
&:before, &:after {
position: absolute;
top: 50%;
left: 50%;
width: 30px;
height: 1px;
background-color: #666;
content: “”;
}
@media screen and (min-width: 992px) {
width: 80px;
height: 120px;
&:before, &:after {
width: 40px;
}
}
&.slick-prev {
left: 0;
&:before, &:after {
transform-origin: left center;
}
&:before {
transform: translate(-50%, -50%) rotate(45deg);
}
&:after {
transform: translate(-50%, -50%) rotate(-45deg);
}
}
&.slick-next {
right: 0;
&:before, &:after {
transform-origin: right center;
}
&:before {
transform: translate(-50%, -50%) rotate(45deg);
}
&:after {
transform: translate(-50%, -50%) rotate(-45deg);
}
}
}
&__main {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
> .carousel-item {
&:first-child:not([class*=’slick’]) {
z-index: 10;
}
&:not(:first-child):not([class*=’slick’]) {
position: absolute !important;
top: 0;
left: 0;
opacity: 0;
}
}
}
&__thumbnail {
margin: 4px -2px 0;
.carousel-item {
padding-left: 2px;
padding-right: 2px;
> .img-wrap {
transition: 0.3s;
cursor: pointer;
> .img {
background-color: #333;
}
}
&:not(.active):not(:hover):not(:focus) > .img-wrap {
opacity: 0.4;
}
}
@media screen and (min-width: 768px) {
margin: 14px -7px 0;
.carousel-item {
padding-left: 7px;
padding-right: 7px;
}
}
}
/* ↓ Slick適用前の調整(段落ち防止処置) ↓ */
&__main > .carousel-item {
&:first-child:not([class*=’slick’]) {
z-index: 10;
}
&:not(:first-child):not([class*=’slick’]) {
position: absolute !important;
top: 0;
left: 0;
opacity: 0;
}
}
&__thumbnail > .carousel-item {
float: left;
width: 33.3333333%;
@media screen and (min-width: 768px) {
width: 25%;
}
@media screen and (min-width: 768px) and (max-width: 991px) {
&:nth-child(n+5) { display:none !important; }
}
@media screen and (min-width: 992px) {
width: 16.6666666%;
&:nth-child(n+7) { display:none !important; }
}
@media screen and (max-width: 767px) {
&:nth-child(n+4) { display:none !important; }
}
}
}
CSS
#imgViewer .carousel-wrap__main {
overflow: hidden;
padding-top: 56.25%;
}
#imgViewer .carousel-wrap__thumbnail {
overflow: hidden;
}
#imgViewer .carousel-outline__thumbnail > .slick-arrow {
width: 40px;
height: 80px;
}
#imgViewer .carousel-outline__thumbnail > .slick-arrow.slick-prev {
left: -47px;
}
#imgViewer .carousel-outline__thumbnail > .slick-arrow.slick-next {
right: -47px;
}
#imgViewer .carousel-outline__thumbnail > .slick-arrow:before, #imgViewer .carousel-outline__thumbnail > .slick-arrow:after {
width: 20px;
}
#imgViewer .carousel-item > .img-wrap > .img {
position: relative;
overflow: hidden;
width: 100%;
height: 0;
padding-top: 56.25%;
font-size: 0.5em;
text-align: left;
text-indent: -9999px;
background: #ccc center no-repeat;
background-size: contain;
}
#imgViewer .carousel-item > .img-wrap > .img:not(div) {
display: block;
}
#imgViewer .slick-arrow {
position: absolute;
top: 50%;
border: none;
background: none;
outline: 0;
width: 60px;
height: 90px;
font-size: 0.5em;
text-align: left;
text-indent: -9999px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
z-index: 10;
}
#imgViewer .slick-arrow:before, #imgViewer .slick-arrow:after {
position: absolute;
top: 50%;
left: 50%;
width: 30px;
height: 1px;
background-color: #666;
content: “”;
}
@media screen and (min-width: 992px) {
#imgViewer .slick-arrow {
width: 80px;
height: 120px;
}
#imgViewer .slick-arrow:before, #imgViewer .slick-arrow:after {
width: 40px;
}
}
#imgViewer .slick-arrow.slick-prev {
left: 0;
}
#imgViewer .slick-arrow.slick-prev:before, #imgViewer .slick-arrow.slick-prev:after {
-webkit-transform-origin: left center;
transform-origin: left center;
}
#imgViewer .slick-arrow.slick-prev:before {
-webkit-transform: translate(-50%, -50%) rotate(45deg);
transform: translate(-50%, -50%) rotate(45deg);
}
#imgViewer .slick-arrow.slick-prev:after {
-webkit-transform: translate(-50%, -50%) rotate(-45deg);
transform: translate(-50%, -50%) rotate(-45deg);
}
#imgViewer .slick-arrow.slick-next {
right: 0;
}
#imgViewer .slick-arrow.slick-next:before, #imgViewer .slick-arrow.slick-next:after {
-webkit-transform-origin: right center;
transform-origin: right center;
}
#imgViewer .slick-arrow.slick-next:before {
-webkit-transform: translate(-50%, -50%) rotate(45deg);
transform: translate(-50%, -50%) rotate(45deg);
}
#imgViewer .slick-arrow.slick-next:after {
-webkit-transform: translate(-50%, -50%) rotate(-45deg);
transform: translate(-50%, -50%) rotate(-45deg);
}
#imgViewer__main {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
#imgViewer__main > .carousel-item:first-child:not([class*=’slick’]) {
z-index: 10;
}
#imgViewer__main > .carousel-item:not(:first-child):not([class*=’slick’]) {
position: absolute !important;
top: 0;
left: 0;
opacity: 0;
}
#imgViewer__thumbnail {
margin: 4px -2px 0;
}
#imgViewer__thumbnail .carousel-item {
padding-left: 2px;
padding-right: 2px;
}
#imgViewer__thumbnail .carousel-item > .img-wrap {
-webkit-transition: 0.3s;
transition: 0.3s;
cursor: pointer;
}
#imgViewer__thumbnail .carousel-item > .img-wrap:not(.active):not(:hover):not(:focus) {
opacity: 0.4;
}
#imgViewer__thumbnail .carousel-item > .img-wrap > .img {
background-color: #333;
}
@media screen and (min-width: 768px) {
#imgViewer__thumbnail {
margin: 14px -7px 0;
}
#imgViewer__thumbnail .carousel-item {
padding-left: 7px;
padding-right: 7px;
}
}
#imgViewer__main > .carousel-item:first-child:not([class*=’slick’]) {
z-index: 10;
}
#imgViewer__main > .carousel-item:not(:first-child):not([class*=’slick’]) {
position: absolute !important;
top: 0;
left: 0;
opacity: 0;
}
#imgViewer__thumbnail > .carousel-item {
float: left;
width: 33.3333333%;
}
@media screen and (min-width: 768px) {
#imgViewer__thumbnail > .carousel-item {
width: 25%;
}
}
@media screen and (min-width: 768px) and (max-width: 991px) {
#imgViewer__thumbnail > .carousel-item:nth-child(n+5) {
display: none !important;
}
}
@media screen and (min-width: 992px) {
#imgViewer__thumbnail > .carousel-item {
width: 16.6666666%;
}
#imgViewer__thumbnail > .carousel-item:nth-child(n+7) {
display: none !important;
}
}
@media screen and (max-width: 767px) {
#imgViewer__thumbnail > .carousel-item:nth-child(n+4) {
display: none !important;
}
}