app/template/default/index.twig line 1

Open in your IDE?
  1. {% extends 'default_frame.twig' %}
  2. {% set body_class = 'front_page' %}
  3. {% block main %}
  4. <div class="mycarousel-wrapper">
  5.     <div class="mycarousel-slider">
  6.         {# PC用画像リスト #}
  7.         {% set pc_images = [
  8.             'main-visual.jpg',
  9.             'S__23207939.jpg',
  10.             'NaiPC.jpg',
  11.             'caPC.jpg',
  12.             'panPC.jpg'
  13.         ] %}
  14.         
  15.         {# SP用画像リスト #}
  16.         {% set sp_images = [
  17.             'sp-topImages.jpg',
  18.             'S__23224325.jpg',
  19.             'NaiSP.jpg',
  20.             'caSP.jpg',
  21.             'panSP.jpg'
  22.         ] %}
  23.         
  24.         {# リンク先URLリスト(画像の順番に対応) #}
  25.         {% set link_urls = [
  26.             'https://iseyabread.com/',
  27.             'https://iseyabread.com/',
  28.             'https://iseyabread.com/products/detail/10',
  29.             'https://iseyabread.com/products/detail/7',            
  30.             'https://iseyabread.com/products/detail/9'
  31.         
  32.         ] %}
  33.         
  34.         {# リンクターゲット設定(_self: 同じタブ, _blank: 新しいタブ) #}
  35.         {% set link_targets = [
  36.             '_self',
  37.             '_self',
  38.             '_self',            
  39.             '_self',            
  40.             '_self',
  41.         ] %}
  42.         
  43.         {# 実際に表示する画像を選択(0から始まるインデックス番号で指定) #}
  44.         {% set display_images = [0,1,2,3,4] %}
  45.         
  46.         {% for index in display_images %}
  47.             <div class="mycarousel-slide">
  48.                 {% if link_urls[index] %}
  49.                     <a href="{{ link_urls[index] }}" target="{{ link_targets[index] ?? '_self' }}" class="carousel-link">
  50.                         <img class="desktop-image" src="{{ asset('/html/user_data/ContentImg/' ~ pc_images[index]) }}" alt="スライド画像">
  51.                         <img class="mobile-image" src="{{ asset('/html/user_data/ContentImg/' ~ sp_images[index]) }}" alt="スライド画像">
  52.                     </a>
  53.                 {% else %}
  54.                     <img class="desktop-image" src="{{ asset('/html/user_data/ContentImg/' ~ pc_images[index]) }}" alt="スライド画像">
  55.                     <img class="mobile-image" src="{{ asset('/html/user_data/ContentImg/' ~ sp_images[index]) }}" alt="スライド画像">
  56.                 {% endif %}
  57.             </div>
  58.         {% endfor %}
  59.     </div>
  60. </div>
  61. {% endblock %}
  62. {% block stylesheet %}
  63. <style>
  64. /* slickのデフォルト黒丸(before擬似要素)を無効化 */
  65. .mycarousel-slider .slick-dots li button::before {
  66.     display: none;
  67.     content: '';
  68. }
  69. .slick-dots li {
  70.     position:unset;
  71.     display: inline-block;
  72.     width: 20px;
  73.     height: 20px;
  74.     margin: 0 5px;
  75.     padding: 0;
  76.     cursor: pointer;
  77. }
  78. .mycarousel-slider .slick-dots li {
  79.     width: 10px;
  80.     height: 10px;
  81. }
  82. .mycarousel-slider .slick-dots li button {
  83.     width: 100%;
  84.     height: 100%;
  85.     padding: 0;
  86.     border: none;
  87.     border-radius: 50%;
  88.     background-color: #ccc;
  89.     opacity: 0.7;
  90.     transition: all 0.3s ease;
  91.     font-size: 0;
  92.     cursor: pointer;
  93. }
  94. .mycarousel-slider .slick-dots li.slick-active button {
  95.     background-color: #b8860b;
  96.     transform: scale(1.3);
  97.     opacity: 1;
  98. }
  99. /* カルーセルリンクのスタイル */
  100. .carousel-link {
  101.     display: block;
  102.     position: relative;
  103.     width: 100%;
  104.     height: 100%;
  105.     text-decoration: none;
  106.     cursor: pointer;
  107.     transition: opacity 0.3s ease;
  108. }
  109. .carousel-link:hover {
  110.     opacity: 0.9;
  111. }
  112. .mycarousel-wrapper {
  113.     width: 100vw;
  114.     max-width: none;
  115.     margin-left: calc(-50vw + 50%);
  116.     margin-right: calc(-50vw + 50%);
  117.     overflow: hidden;
  118.     padding: 0;
  119.     /* 初期化時のちらつき防止 */
  120.     visibility: hidden;
  121. }
  122. .slick-track {
  123.     display: flex !important;
  124.     align-items: center;
  125. }
  126. .slick-slide {
  127.     padding: 0 !important;
  128.     margin: 0 !important;
  129. }
  130. /* スライド本体(動的高さ対応) */
  131. .mycarousel-slide {
  132.     box-sizing: border-box;
  133.     padding: 0 !important;
  134.     margin: 0 !important;
  135.     position: relative;
  136.     transition: all 0.5s ease;
  137.     overflow: hidden;
  138.     height: 60vh; /* 初期値:JavaScript で調整される */
  139. }
  140. /* 画像の基本スタイル */
  141. .desktop-image,
  142. .mobile-image {
  143.     position: absolute;
  144.     top: 0;
  145.     left: 0;
  146.     width: 100%;
  147.     height: 100%;
  148.     transition: all 0.5s ease;
  149.     /* object-fit は JavaScript で動的に調整 */
  150.     object-fit: contain;
  151. }
  152. /* デスクトップ時のスタイル(768pxより大きい) */
  153. @media (min-width: 769px) {
  154.     .desktop-image {
  155.         display: block !important;
  156.     }
  157.     .mobile-image {
  158.         display: none !important;
  159.     }
  160. }
  161. /* モバイル時のスタイル(768px以下) */
  162. @media (max-width: 768px) {
  163.     .mycarousel-wrapper {
  164.         width: 100vw;
  165.         margin-left: calc(-50vw + 50%);
  166.         margin-right: calc(-50vw + 50%);
  167.     }
  168.     
  169.     .mycarousel-slide {
  170.         width: 100vw !important;
  171.         height: auto !important;
  172.         padding: 0 !important;
  173.         margin: 0 !important;
  174.         opacity: 1 !important;
  175.         filter: none !important;
  176.     }
  177.     .desktop-image {
  178.         display: none !important;
  179.     }
  180.     
  181.     .mobile-image {
  182.         display: block !important;
  183.         position: relative !important;
  184.         width: 100% !important;
  185.         height: auto !important;
  186.         object-fit: contain !important;
  187.         filter: brightness(1) contrast(1) !important;
  188.     }
  189. }
  190. /* 左右の画像を暗くするエフェクト(デスクトップのみ) */
  191. @media (min-width: 769px) {
  192.     .mycarousel-slide.side-slide {
  193.         opacity: 0.6;
  194.     }
  195.     .mycarousel-slide.side-slide .desktop-image {
  196.         filter: brightness(0.7) contrast(0.9);
  197.     }
  198.     .mycarousel-slide.center-slide {
  199.         opacity: 1;
  200.     }
  201.     .mycarousel-slide.center-slide .desktop-image {
  202.         filter: brightness(1) contrast(1);
  203.     }
  204.     .mycarousel-slide.side-slide:hover {
  205.         opacity: 0.8;
  206.         transform: scale(1.02);
  207.     }
  208.     .mycarousel-slide.side-slide:hover .desktop-image {
  209.         filter: brightness(0.85) contrast(0.95);
  210.     }
  211. }
  212. /* 動的高さ調整用のユーティリティクラス */
  213. .dynamic-fit-contain {
  214.     object-fit: contain;
  215. }
  216. .dynamic-fit-cover {
  217.     object-fit: cover;
  218. }
  219. </style>
  220. {% endblock %}
  221. {% block javascript %}
  222. <script>
  223. $(function() {
  224.     // 動的レスポンシブ設定
  225.     const RESPONSIVE_CONFIG = {
  226.         desktop: {
  227.             targetAspectRatio: 16/9,
  228.             minHeight: '30vh',
  229.             maxHeight: '60vh',
  230.             marginThreshold: 0.15
  231.         },
  232.         mobile: {
  233.             targetAspectRatio: 4/3,
  234.             minHeight: '35vh',
  235.             maxHeight: '85vh',
  236.             marginThreshold: 0.12
  237.         }
  238.     };
  239.     let isInitialized = false;
  240.     let currentImages = [];
  241.     // 画面幅に応じた動的centerPadding計算
  242.     function calculateCenterPadding() {
  243.         const windowWidth = $(window).width();
  244.         let padding;
  245.         
  246.         // 画面幅に応じてcenterPaddingを計算(調整可能な閾値)
  247.         if (windowWidth <= 768) {
  248.             // モバイル: 余白なし
  249.             padding = '0';
  250.         } else if (windowWidth <= 1024) {
  251.             // タブレット: 小さめの余白
  252.             padding = '8vw';
  253.         } else if (windowWidth <= 1200) {
  254.             // 小型デスクトップ: 中程度の余白
  255.             padding = '12vw';
  256.         }else if (windowWidth <= 1440) {
  257.             // 小型デスクトップ: 中程度の余白
  258.             padding = '15vw';
  259.         
  260.         } else if (windowWidth <= 1920) {
  261.             // 標準デスクトップ: やや大きめの余白
  262.             padding = '20vw';
  263.         } else {
  264.             // 大型ディスプレイ: 最大余白を制限
  265.             padding = '25vw';
  266.         }
  267.         
  268.         return padding;
  269.     }
  270.     // centerPaddingを動的に更新する関数
  271.     function updateCenterPadding() {
  272.         const newPadding = calculateCenterPadding();
  273.         const windowWidth = $(window).width();
  274.         
  275.         // モバイルはcenterMode自体を無効にする
  276.         const centerModeEnabled = windowWidth > 768;
  277.         
  278.         if ($('.mycarousel-slider').hasClass('slick-initialized')) {
  279.             $('.mycarousel-slider').slick('slickSetOption', 'centerMode', centerModeEnabled, false);
  280.             $('.mycarousel-slider').slick('slickSetOption', 'centerPadding', newPadding, true);
  281.         }
  282.     }
  283.     // 現在のデバイスに応じた画像を取得
  284.     function getCurrentImages() {
  285.         const isMobile = $(window).width() <= 768;
  286.         return isMobile ? $('.mobile-image') : $('.desktop-image');
  287.     }
  288.     // 画像プリロード関数
  289.     function preloadImages() {
  290.         return new Promise((resolve) => {
  291.             const images = getCurrentImages();
  292.             let loadedCount = 0;
  293.             const totalImages = images.length;
  294.             if (totalImages === 0) {
  295.                 resolve();
  296.                 return;
  297.             }
  298.             images.each(function() {
  299.                 const img = this;
  300.                 
  301.                 if (img.complete && img.naturalWidth > 0) {
  302.                     loadedCount++;
  303.                     if (loadedCount >= totalImages) {
  304.                         resolve();
  305.                     }
  306.                 } else {
  307.                     $(img).on('load error', function() {
  308.                         loadedCount++;
  309.                         if (loadedCount >= totalImages) {
  310.                             resolve();
  311.                         }
  312.                     });
  313.                     
  314.                     // 強制的に再読み込みをトリガー(キャッシュ対策)
  315.                     if (!img.src) {
  316.                         img.src = img.getAttribute('src');
  317.                     }
  318.                 }
  319.             });
  320.         });
  321.     }
  322.     // スライダー初期化(動的centerPadding対応版)
  323.     function initializeSlider() {
  324.         if ($('.mycarousel-slider').hasClass('slick-initialized')) {
  325.             $('.mycarousel-slider').slick('unslick');
  326.         }
  327.         const initialPadding = calculateCenterPadding();
  328.         const windowWidth = $(window).width();
  329.         const centerModeEnabled = windowWidth > 768;
  330.         $('.mycarousel-slider').slick({
  331.             centerMode: centerModeEnabled,
  332.             centerPadding: initialPadding,
  333.             slidesToShow: 1,
  334.             autoplay: false, // 初期化時は自動再生を無効にして安定性を確保
  335.             autoplaySpeed: 5000,
  336.             arrows: true,
  337.             dots: true,
  338.             adaptiveHeight: false,
  339.             fade: false,
  340.             speed: 300
  341.             // responsive設定は削除(動的制御に切り替え)
  342.         });
  343.         // スライド変更時にクラスを更新
  344.         $('.mycarousel-slider').on('afterChange', function(event, slick, currentSlide) {
  345.             updateSlideClasses();
  346.         });
  347.     }
  348.     // 動的な表示最適化メイン関数
  349.     function optimizeCarouselDisplay() {
  350.         const isMobile = $(window).width() <= 768;
  351.         const config = isMobile ? RESPONSIVE_CONFIG.mobile : RESPONSIVE_CONFIG.desktop;
  352.         const $currentImages = getCurrentImages();
  353.         
  354.         if ($currentImages.length === 0) return;
  355.         // モバイル時は簡単な設定にする
  356.         if (isMobile) {
  357.             // モバイル時:width 100vw、height auto
  358.             $('.mycarousel-slide').css({
  359.                 'height': 'auto',
  360.                 'width': '100vw'
  361.             });
  362.             $currentImages.css({
  363.                 'width': '100%',
  364.                 'height': 'auto',
  365.                 'object-fit': 'contain',
  366.                 'position': 'relative'
  367.             });
  368.             
  369.             // 初期化完了後に自動再生を開始
  370.             if (isInitialized && $('.mycarousel-slider').slick('slickGetOption', 'autoplay') === false) {
  371.                 $('.mycarousel-slider').slick('slickSetOption', 'autoplay', true, true);
  372.             }
  373.             return;
  374.         }
  375.         const $firstImage = $currentImages.filter(':visible').first();
  376.         if ($firstImage.length === 0 || !$firstImage[0].complete || !$firstImage[0].naturalWidth) {
  377.             return;
  378.         }
  379.         const imageWidth = $firstImage[0].naturalWidth;
  380.         const imageHeight = $firstImage[0].naturalHeight;
  381.         const imageAspectRatio = imageWidth / imageHeight;
  382.         
  383.         const containerWidth = $('.mycarousel-wrapper').width();
  384.         const containerAspectRatio = config.targetAspectRatio;
  385.         
  386.         const maxRenderWidth = Math.min(imageWidth, containerWidth);
  387.         const maxRenderHeight = maxRenderWidth / imageAspectRatio;
  388.         
  389.         const viewportMinHeightPx = parseFloat(config.minHeight) * $(window).height() / 100;
  390.         const viewportMaxHeightPx = parseFloat(config.maxHeight) * $(window).height() / 100;
  391.         
  392.         let optimalHeight;
  393.         let objectFitMode = 'contain';
  394.         const aspectRatioDiff = Math.abs(imageAspectRatio - containerAspectRatio) / containerAspectRatio;
  395.         if (maxRenderHeight <= viewportMaxHeightPx && maxRenderHeight >= viewportMinHeightPx) {
  396.             optimalHeight = maxRenderHeight;
  397.         } else if (maxRenderHeight > viewportMaxHeightPx) {
  398.             if (aspectRatioDiff <= config.marginThreshold) {
  399.                 optimalHeight = viewportMaxHeightPx;
  400.             } else {
  401.                 optimalHeight = Math.min(containerWidth / imageAspectRatio, viewportMaxHeightPx);
  402.             }
  403.         } else {
  404.             if (maxRenderHeight < viewportMinHeightPx) {
  405.                 const scaleUpLimit = Math.min(2.0, viewportMinHeightPx / maxRenderHeight);
  406.                 if (scaleUpLimit <= 1.5) {
  407.                     optimalHeight = viewportMinHeightPx;
  408.                 } else {
  409.                     optimalHeight = maxRenderHeight;
  410.                 }
  411.             } else {
  412.                 optimalHeight = maxRenderHeight;
  413.             }
  414.         }
  415.         // 最終的な制限適用
  416.         optimalHeight = Math.max(viewportMinHeightPx, Math.min(viewportMaxHeightPx, optimalHeight));
  417.         // 画像の品質を考慮したobject-fit調整
  418.         const renderScale = optimalHeight / maxRenderHeight;
  419.         if (renderScale > 1.2) {
  420.             objectFitMode = 'contain';
  421.         } else {
  422.             objectFitMode = 'contain';
  423.         }
  424.         // スタイル適用(デスクトップのみ)
  425.         $('.mycarousel-slide').css('height', optimalHeight + 'px');
  426.         $currentImages.css({
  427.             'object-fit': objectFitMode,
  428.             'image-rendering': renderScale > 1.1 ? 'smooth' : 'auto'
  429.         });
  430.         // 初期化完了後に自動再生を開始
  431.         if (isInitialized && $('.mycarousel-slider').slick('slickGetOption', 'autoplay') === false) {
  432.             $('.mycarousel-slider').slick('slickSetOption', 'autoplay', true, true);
  433.         }
  434.     }
  435.     // スライドのクラスを更新する関数
  436.     function updateSlideClasses() {
  437.         $('.mycarousel-slide').removeClass('center-slide side-slide');
  438.         
  439.         if ($(window).width() > 768) {
  440.             $('.slick-center .mycarousel-slide').addClass('center-slide');
  441.             $('.slick-slide:not(.slick-center) .mycarousel-slide').addClass('side-slide');
  442.         } else {
  443.             $('.mycarousel-slide').addClass('center-slide');
  444.         }
  445.     }
  446.     async function initialize() {
  447.         try {
  448.             // カルーセルを一時的に非表示にしてちらつき防止
  449.             $('.mycarousel-wrapper').css('visibility', 'hidden');
  450.             
  451.             await preloadImages();
  452.             
  453.             initializeSlider();
  454.             
  455.             optimizeCarouselDisplay();
  456.             
  457.             updateSlideClasses();
  458.             
  459.             isInitialized = true;
  460.             
  461.             // 6. 少し待ってから表示(slickの初期化完了を確実にする)
  462.             setTimeout(() => {
  463.                 $('.mycarousel-wrapper').css('visibility', 'visible');
  464.                 // 初期化後にもう一度表示を最適化
  465.                 optimizeCarouselDisplay();
  466.                 updateSlideClasses();
  467.             }, 50);
  468.             
  469.             console.log('Carousel initialized successfully');
  470.             
  471.         } catch (error) {
  472.             console.error('Carousel initialization failed:', error);
  473.             // エラー時も表示を戻す
  474.             $('.mycarousel-wrapper').css('visibility', 'visible');
  475.         }
  476.     }
  477.     let resizeTimer;
  478.     $(window).on('resize', function() {
  479.         clearTimeout(resizeTimer);
  480.         resizeTimer = setTimeout(async () => {
  481.             // centerPaddingを動的に更新
  482.             updateCenterPadding();
  483.             
  484.             // デバイス切り替わり時は再初期化
  485.             const newImages = getCurrentImages();
  486.             const imagesSwitched = !currentImages.length || 
  487.                 newImages.length !== currentImages.length ||
  488.                 newImages[0] !== currentImages[0];
  489.             
  490.             if (imagesSwitched) {
  491.                 currentImages = newImages;
  492.                 await preloadImages();
  493.             }
  494.             
  495.             optimizeCarouselDisplay();
  496.             updateSlideClasses();
  497.         }, 200);
  498.     });
  499.     
  500.     $(document).ready(function() {
  501.         currentImages = getCurrentImages();
  502.         initialize();
  503.     });
  504.     
  505.     $(window).on('load', function() {
  506.         if (!isInitialized) {
  507.             initialize();
  508.         }
  509.     });
  510. });
  511. </script>
  512. {% endblock %}