{% extends 'default_frame.twig' %}
{% set body_class = 'front_page' %}
{% block main %}
<div class="mycarousel-wrapper">
<div class="mycarousel-slider">
{# PC用画像リスト main-visual.jpg #}
{% set pc_images = [
'main-visual.jpg',
'main-visual.jpg',
'main-visual.jpg'
] %}
{# SP用画像リスト sp-topImages.jpg #}
{% set sp_images = [
'sp-topImages.jpg',
'sp-topImages.jpg',
'sp-topImages.jpg'
] %}
{# 実際に表示する画像を選択(0から始まるインデックス番号で指定) #}
{% set display_images = [0,1,2] %}
{% for index in display_images %}
<div class="mycarousel-slide">
<img class="desktop-image" src="{{ asset('/html/user_data/ContentImg/' ~ pc_images[index]) }}" alt="スライド画像">
<img class="mobile-image" src="{{ asset('/html/user_data/ContentImg/' ~ sp_images[index]) }}" alt="スライド画像">
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block stylesheet %}
<style>
/* slickのデフォルト黒丸(before擬似要素)を無効化 */
.mycarousel-slider .slick-dots li button::before {
display: none;
content: '';
}
.slick-dots li {
position:unset;
display: inline-block;
width: 20px;
height: 20px;
margin: 0 5px;
padding: 0;
cursor: pointer;
}
.mycarousel-slider .slick-dots li {
width: 10px;
height: 10px;
}
.mycarousel-slider .slick-dots li button {
width: 100%;
height: 100%;
padding: 0;
border: none;
border-radius: 50%;
background-color: #ccc;
opacity: 0.7;
transition: all 0.3s ease;
font-size: 0;
cursor: pointer;
}
.mycarousel-slider .slick-dots li.slick-active button {
background-color: #b8860b;
transform: scale(1.3);
opacity: 1;
}
/* フルワイドの外枠 */
.mycarousel-wrapper {
width: 100vw;
max-width: none;
margin-left: calc(-50vw + 50%);
margin-right: calc(-50vw + 50%);
overflow: hidden;
padding: 0;
/* 初期化時のちらつき防止 */
visibility: hidden;
}
/* slickのトラック修正 */
.slick-track {
display: flex !important;
align-items: center;
}
.slick-slide {
padding: 0 !important;
margin: 0 !important;
}
/* スライド本体(動的高さ対応) */
.mycarousel-slide {
box-sizing: border-box;
padding: 0 !important;
margin: 0 !important;
position: relative;
transition: all 0.5s ease;
overflow: hidden;
/* 高さは動的に設定される */
height: 60vh; /* 初期値:JavaScript で調整される */
}
/* 画像の基本スタイル */
.desktop-image,
.mobile-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: all 0.5s ease;
/* object-fit は JavaScript で動的に調整 */
object-fit: contain;
}
/* デスクトップ時のスタイル(768pxより大きい) */
@media (min-width: 769px) {
.desktop-image {
display: block !important;
}
.mobile-image {
display: none !important;
}
}
/* モバイル時のスタイル(768px以下) */
@media (max-width: 768px) {
.mycarousel-wrapper {
width: 100vw;
margin-left: calc(-50vw + 50%);
margin-right: calc(-50vw + 50%);
}
.mycarousel-slide {
width: 100vw !important;
height: auto !important;
padding: 0 !important;
margin: 0 !important;
opacity: 1 !important;
filter: none !important;
}
.desktop-image {
display: none !important;
}
.mobile-image {
display: block !important;
position: relative !important;
width: 100% !important;
height: auto !important;
object-fit: contain !important;
filter: brightness(1) contrast(1) !important;
}
}
/* 左右の画像を暗くするエフェクト(デスクトップのみ) */
@media (min-width: 769px) {
.mycarousel-slide.side-slide {
opacity: 0.6;
}
.mycarousel-slide.side-slide .desktop-image {
filter: brightness(0.7) contrast(0.9);
}
.mycarousel-slide.center-slide {
opacity: 1;
}
.mycarousel-slide.center-slide .desktop-image {
filter: brightness(1) contrast(1);
}
.mycarousel-slide.side-slide:hover {
opacity: 0.8;
transform: scale(1.02);
}
.mycarousel-slide.side-slide:hover .desktop-image {
filter: brightness(0.85) contrast(0.95);
}
}
/* 動的高さ調整用のユーティリティクラス */
.dynamic-fit-contain {
object-fit: contain;
}
.dynamic-fit-cover {
object-fit: cover;
}
</style>
{% endblock %}
{% block javascript %}
<script>
$(function() {
// 動的レスポンシブ設定
const RESPONSIVE_CONFIG = {
desktop: {
targetAspectRatio: 16/9,
minHeight: '30vh',
maxHeight: '60vh',
marginThreshold: 0.15
},
mobile: {
targetAspectRatio: 4/3,
minHeight: '35vh',
maxHeight: '85vh',
marginThreshold: 0.12
}
};
let isInitialized = false;
let currentImages = [];
// 画面幅に応じた動的centerPadding計算
function calculateCenterPadding() {
const windowWidth = $(window).width();
let padding;
// 画面幅に応じてcenterPaddingを計算(調整可能な閾値)
if (windowWidth <= 768) {
// モバイル: 余白なし
padding = '0';
} else if (windowWidth <= 1024) {
// タブレット: 小さめの余白
padding = '8vw';
} else if (windowWidth <= 1200) {
// 小型デスクトップ: 中程度の余白
padding = '12vw';
}else if (windowWidth <= 1440) {
// 小型デスクトップ: 中程度の余白
padding = '15vw';
} else if (windowWidth <= 1920) {
// 標準デスクトップ: やや大きめの余白
padding = '20vw';
} else {
// 大型ディスプレイ: 最大余白を制限
padding = '25vw';
}
return padding;
}
// centerPaddingを動的に更新する関数
function updateCenterPadding() {
const newPadding = calculateCenterPadding();
const windowWidth = $(window).width();
// モバイルはcenterMode自体を無効にする
const centerModeEnabled = windowWidth > 768;
if ($('.mycarousel-slider').hasClass('slick-initialized')) {
$('.mycarousel-slider').slick('slickSetOption', 'centerMode', centerModeEnabled, false);
$('.mycarousel-slider').slick('slickSetOption', 'centerPadding', newPadding, true);
}
}
// 現在のデバイスに応じた画像を取得
function getCurrentImages() {
const isMobile = $(window).width() <= 768;
return isMobile ? $('.mobile-image') : $('.desktop-image');
}
// 画像プリロード関数
function preloadImages() {
return new Promise((resolve) => {
const images = getCurrentImages();
let loadedCount = 0;
const totalImages = images.length;
if (totalImages === 0) {
resolve();
return;
}
images.each(function() {
const img = this;
if (img.complete && img.naturalWidth > 0) {
loadedCount++;
if (loadedCount >= totalImages) {
resolve();
}
} else {
$(img).on('load error', function() {
loadedCount++;
if (loadedCount >= totalImages) {
resolve();
}
});
// 強制的に再読み込みをトリガー(キャッシュ対策)
if (!img.src) {
img.src = img.getAttribute('src');
}
}
});
});
}
// スライダー初期化(動的centerPadding対応版)
function initializeSlider() {
if ($('.mycarousel-slider').hasClass('slick-initialized')) {
$('.mycarousel-slider').slick('unslick');
}
const initialPadding = calculateCenterPadding();
const windowWidth = $(window).width();
const centerModeEnabled = windowWidth > 768;
$('.mycarousel-slider').slick({
centerMode: centerModeEnabled,
centerPadding: initialPadding,
slidesToShow: 1,
autoplay: false, // 初期化時は自動再生を無効にして安定性を確保
autoplaySpeed: 5000,
arrows: true,
dots: true,
adaptiveHeight: false,
fade: false,
speed: 300
// responsive設定は削除(動的制御に切り替え)
});
// スライド変更時にクラスを更新
$('.mycarousel-slider').on('afterChange', function(event, slick, currentSlide) {
updateSlideClasses();
});
}
// 動的な表示最適化メイン関数
function optimizeCarouselDisplay() {
const isMobile = $(window).width() <= 768;
const config = isMobile ? RESPONSIVE_CONFIG.mobile : RESPONSIVE_CONFIG.desktop;
const $currentImages = getCurrentImages();
if ($currentImages.length === 0) return;
// モバイル時は簡単な設定にする
if (isMobile) {
// モバイル時:width 100vw、height auto
$('.mycarousel-slide').css({
'height': 'auto',
'width': '100vw'
});
$currentImages.css({
'width': '100%',
'height': 'auto',
'object-fit': 'contain',
'position': 'relative'
});
// 初期化完了後に自動再生を開始
if (isInitialized && $('.mycarousel-slider').slick('slickGetOption', 'autoplay') === false) {
$('.mycarousel-slider').slick('slickSetOption', 'autoplay', true, true);
}
return;
}
const $firstImage = $currentImages.filter(':visible').first();
if ($firstImage.length === 0 || !$firstImage[0].complete || !$firstImage[0].naturalWidth) {
return;
}
const imageWidth = $firstImage[0].naturalWidth;
const imageHeight = $firstImage[0].naturalHeight;
const imageAspectRatio = imageWidth / imageHeight;
const containerWidth = $('.mycarousel-wrapper').width();
const containerAspectRatio = config.targetAspectRatio;
const maxRenderWidth = Math.min(imageWidth, containerWidth);
const maxRenderHeight = maxRenderWidth / imageAspectRatio;
const viewportMinHeightPx = parseFloat(config.minHeight) * $(window).height() / 100;
const viewportMaxHeightPx = parseFloat(config.maxHeight) * $(window).height() / 100;
let optimalHeight;
let objectFitMode = 'contain';
const aspectRatioDiff = Math.abs(imageAspectRatio - containerAspectRatio) / containerAspectRatio;
if (maxRenderHeight <= viewportMaxHeightPx && maxRenderHeight >= viewportMinHeightPx) {
optimalHeight = maxRenderHeight;
} else if (maxRenderHeight > viewportMaxHeightPx) {
if (aspectRatioDiff <= config.marginThreshold) {
optimalHeight = viewportMaxHeightPx;
} else {
optimalHeight = Math.min(containerWidth / imageAspectRatio, viewportMaxHeightPx);
}
} else {
if (maxRenderHeight < viewportMinHeightPx) {
const scaleUpLimit = Math.min(2.0, viewportMinHeightPx / maxRenderHeight);
if (scaleUpLimit <= 1.5) {
optimalHeight = viewportMinHeightPx;
} else {
optimalHeight = maxRenderHeight;
}
} else {
optimalHeight = maxRenderHeight;
}
}
// 最終的な制限適用
optimalHeight = Math.max(viewportMinHeightPx, Math.min(viewportMaxHeightPx, optimalHeight));
// 画像の品質を考慮したobject-fit調整
const renderScale = optimalHeight / maxRenderHeight;
if (renderScale > 1.2) {
objectFitMode = 'contain';
} else {
objectFitMode = 'contain';
}
// スタイル適用(デスクトップのみ)
$('.mycarousel-slide').css('height', optimalHeight + 'px');
$currentImages.css({
'object-fit': objectFitMode,
'image-rendering': renderScale > 1.1 ? 'smooth' : 'auto'
});
// 初期化完了後に自動再生を開始
if (isInitialized && $('.mycarousel-slider').slick('slickGetOption', 'autoplay') === false) {
$('.mycarousel-slider').slick('slickSetOption', 'autoplay', true, true);
}
}
// スライドのクラスを更新する関数
function updateSlideClasses() {
$('.mycarousel-slide').removeClass('center-slide side-slide');
if ($(window).width() > 768) {
$('.slick-center .mycarousel-slide').addClass('center-slide');
$('.slick-slide:not(.slick-center) .mycarousel-slide').addClass('side-slide');
} else {
$('.mycarousel-slide').addClass('center-slide');
}
}
async function initialize() {
try {
// カルーセルを一時的に非表示にしてちらつき防止
$('.mycarousel-wrapper').css('visibility', 'hidden');
await preloadImages();
initializeSlider();
optimizeCarouselDisplay();
updateSlideClasses();
isInitialized = true;
// 6. 少し待ってから表示(slickの初期化完了を確実にする)
setTimeout(() => {
$('.mycarousel-wrapper').css('visibility', 'visible');
// 初期化後にもう一度表示を最適化
optimizeCarouselDisplay();
updateSlideClasses();
}, 50);
console.log('Carousel initialized successfully');
} catch (error) {
console.error('Carousel initialization failed:', error);
// エラー時も表示を戻す
$('.mycarousel-wrapper').css('visibility', 'visible');
}
}
let resizeTimer;
$(window).on('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(async () => {
// centerPaddingを動的に更新
updateCenterPadding();
// デバイス切り替わり時は再初期化
const newImages = getCurrentImages();
const imagesSwitched = !currentImages.length ||
newImages.length !== currentImages.length ||
newImages[0] !== currentImages[0];
if (imagesSwitched) {
currentImages = newImages;
await preloadImages();
}
optimizeCarouselDisplay();
updateSlideClasses();
}, 200);
});
$(document).ready(function() {
currentImages = getCurrentImages();
initialize();
});
$(window).on('load', function() {
if (!isInitialized) {
initialize();
}
});
});
</script>
{% endblock %}