Slide-mode images rendered at intrinsic ratio, top-aligned inside the swiper chain (--pc-height:auto left .swiper-wrapper content-sized), so the taller box showed a gap at the bottom and never center-cropped like the image cover. The video cover had the same problem through video-player.js (height:auto, capped at 70vh). - aksrt_render_slider scale mode emits --pc-height/--m-height 100% so the wrapper/slide/img chain fills the ratio box and object-fit: cover center-crops inside it (same pattern as the .scale-height widgets) - video-player.js renders the video at height:100% + object-fit:cover; with an undimensioned container height:100% degrades to auto, matching the previous behavior - drop the dead .single-cover padding-bottom rules (every producer owns its height inline, and the rule squeezed the absolutely positioned slide inner) and the orphaned --single-cover-scale var in tokens.css/compat-vars.css/main.css/InlineStyleBuilder - rebuild public bundles; extend the single-cover-height contract
240 lines
9.4 KiB
PHP
240 lines
9.4 KiB
PHP
<?php
|
||
/**
|
||
* AKSRT Slider Helper — 轮播渲染函数
|
||
*
|
||
* 对标 legacy 的 legacy(),提供服务端轮播 HTML 生成。
|
||
* 支持 data-* 属性驱动配置、视差、懒加载、缩略图分页。
|
||
*
|
||
* @package AKSRT
|
||
*/
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* 渲染 Swiper 轮播
|
||
*
|
||
* @param array $args {
|
||
* @type array $slides 幻灯片数据数组
|
||
* @type string $effect 动画效果: slide, fade, cube, coverflow, flip (默认 slide)
|
||
* @type bool $loop 是否循环 (默认 true)
|
||
* @type bool $autoplay 是否自动播放 (默认 true)
|
||
* @type int $interval 自动播放间隔 ms (默认 5000)
|
||
* @type int $speed 动画速度 ms (0=自动计算)
|
||
* @type string $direction 方向: horizontal, vertical (默认 horizontal)
|
||
* @type int $spaceBetween 幻灯片间距 px (默认 0)
|
||
* @type string $pagination_type 分页器类型: bullets, fraction, progressbar, thumbnail (默认 bullets)
|
||
* @type string $pc_height PC 端高度 CSS 值 (默认 auto)
|
||
* @type string $m_height 移动端高度 CSS 值 (默认 auto)
|
||
* @type bool $scale_height 是否使用比例高度 (默认 false)
|
||
* @type float $scale_ratio 比例值 (默认 0.45 = 45%)
|
||
* @type string $scale_extra 比例高度附加量,如 '50px'(用于封面标题预留,默认空)
|
||
* @type string $wrapper_class 额外 wrapper 类
|
||
* @type string $slider_class 额外 slider 类
|
||
* @type bool $show_nav 是否显示导航箭头 (默认 true)
|
||
* @type bool $show_pagination 是否显示分页器 (默认 true)
|
||
* }
|
||
*
|
||
* slide 数据格式:
|
||
* [
|
||
* 'image' => 'https://...',
|
||
* 'link' => 'https://...',
|
||
* 'title' => '标题',
|
||
* 'desc' => '描述',
|
||
* 'alt' => '图片 alt',
|
||
* 'bg_color' => '#f0f0f0', // 背景色
|
||
* 'target' => '_blank',
|
||
* ]
|
||
*
|
||
* @return string HTML
|
||
*/
|
||
function aksrt_render_slider(array $args = []): string {
|
||
$defaults = [
|
||
'slides' => [],
|
||
'effect' => 'slide',
|
||
'loop' => true,
|
||
'autoplay' => true,
|
||
'interval' => 5000,
|
||
'speed' => 0,
|
||
'direction' => 'horizontal',
|
||
'spaceBetween' => 0,
|
||
'pagination_type' => 'bullets',
|
||
'pc_height' => 'auto',
|
||
'm_height' => 'auto',
|
||
'scale_height' => false,
|
||
'scale_ratio' => 0.45,
|
||
'scale_extra' => '',
|
||
'wrapper_class' => '',
|
||
'slider_class' => '',
|
||
'show_nav' => true,
|
||
'show_pagination' => true,
|
||
// Optional absolute overlay HTML (e.g. single cover title/meta). Not escaped.
|
||
'overlay_html' => '',
|
||
];
|
||
|
||
$args = wp_parse_args($args, $defaults);
|
||
$slides = $args['slides'];
|
||
|
||
if (empty($slides) || !is_array($slides)) {
|
||
return '';
|
||
}
|
||
|
||
// 构建 data 属性
|
||
$data_attrs = [];
|
||
$data_attrs[] = 'data-effect="' . esc_attr($args['effect']) . '"';
|
||
if ($args['loop']) $data_attrs[] = 'data-loop';
|
||
if ($args['autoplay']) {
|
||
$data_attrs[] = 'data-autoplay';
|
||
$data_attrs[] = 'data-interval="' . esc_attr((string) $args['interval']) . '"';
|
||
}
|
||
if ($args['speed'] > 0) $data_attrs[] = 'data-speed="' . esc_attr((string) $args['speed']) . '"';
|
||
if ($args['direction'] !== 'horizontal') $data_attrs[] = 'data-direction="' . esc_attr($args['direction']) . '"';
|
||
if ($args['spaceBetween'] > 0) $data_attrs[] = 'data-spaceBetween="' . esc_attr((string) $args['spaceBetween']) . '"';
|
||
if ($args['pagination_type'] !== 'bullets') $data_attrs[] = 'data-pagination-type="' . esc_attr($args['pagination_type']) . '"';
|
||
|
||
// 高度样式
|
||
$height_style = '';
|
||
$scale_padding = '';
|
||
if ($args['scale_height']) {
|
||
// padding-bottom percentage for ratio box (legacy graphic cover style).
|
||
// 内层 .new-swiper 绝对定位铺满 wrapper,wrapper 决定盒子高度;
|
||
// pc/m-height 100% 让 .swiper-wrapper/.swiper-slide 铺满盒子,
|
||
// 使 object-fit: cover 在确定的盒内居中裁切(对齐单图封面)。
|
||
$ratio_pct = max(10, min(100, (float) $args['scale_ratio'] * 100));
|
||
$scale_padding = $ratio_pct . '%';
|
||
$scale_extra = trim((string) $args['scale_extra']);
|
||
if ($scale_extra !== '') {
|
||
$scale_padding = 'calc(' . $scale_padding . ' + ' . $scale_extra . ')';
|
||
}
|
||
$height_style = '--pc-height:100%;--m-height:100%;';
|
||
} else {
|
||
$height_style = sprintf('--pc-height:%s;--m-height:%s;', esc_attr($args['pc_height']), esc_attr($args['m_height']));
|
||
}
|
||
|
||
// CSS 类
|
||
$slider_classes = ['new-swiper', 'slide-index'];
|
||
if ($args['slider_class']) $slider_classes[] = $args['slider_class'];
|
||
$slider_class_str = implode(' ', $slider_classes);
|
||
|
||
$wrapper_classes = ['aksrt-slider-wrapper'];
|
||
if ($args['wrapper_class']) $wrapper_classes[] = $args['wrapper_class'];
|
||
if ($args['scale_height']) $wrapper_classes[] = 'aksrt-slider-wrapper--scale';
|
||
if ((string) $args['overlay_html'] !== '') $wrapper_classes[] = 'aksrt-slider-wrapper--has-overlay';
|
||
$wrapper_class_str = implode(' ', $wrapper_classes);
|
||
|
||
// 渲染 HTML
|
||
$html = '<div class="' . esc_attr($wrapper_class_str) . '"' . ($args['scale_height'] ? ' style="position:relative;padding-bottom:' . esc_attr($scale_padding) . ';"' : '') . '>';
|
||
$html .= '<div class="' . esc_attr($slider_class_str) . '" ' . implode(' ', $data_attrs) . ' style="' . esc_attr($height_style . ($args['scale_height'] ? 'position:absolute;inset:0;width:100%;height:100%;' : '')) . '">';
|
||
$html .= '<div class="swiper-wrapper">';
|
||
|
||
foreach ($slides as $slide) {
|
||
$image = esc_url($slide['image'] ?? '');
|
||
$link = esc_url($slide['link'] ?? '');
|
||
$title = sanitize_text_field($slide['title'] ?? '');
|
||
$desc = sanitize_text_field($slide['desc'] ?? '');
|
||
$alt = sanitize_text_field($slide['alt'] ?? $title);
|
||
$bg_color = sanitize_hex_color($slide['bg_color'] ?? '');
|
||
$target = ($slide['target'] ?? '') === '_blank' ? '_blank' : '_self';
|
||
|
||
$slide_style = $bg_color ? 'background-color:' . esc_attr($bg_color) . ';' : '';
|
||
|
||
$html .= '<div class="swiper-slide"' . ($slide_style ? ' style="' . esc_attr($slide_style) . '"' : '') . '>';
|
||
|
||
if ($link) {
|
||
$html .= '<a href="' . esc_url($link) . '" target="' . esc_attr($target) . '">';
|
||
}
|
||
|
||
if ($image) {
|
||
$html .= '<img' . aksrt_get_lazy_attr('lazy_sider', (string) $image, 'fit-cover', aksrt_get_lazy_thumb('lg')) . ' alt="' . esc_attr($alt) . '">';
|
||
}
|
||
|
||
if ($link) {
|
||
$html .= '</a>';
|
||
}
|
||
|
||
// 文字覆盖层
|
||
if ($title || $desc) {
|
||
$html .= '<div class="aksrt-slide-text">';
|
||
if ($title) $html .= '<div class="aksrt-slide-text__title">' . esc_html($title) . '</div>';
|
||
if ($desc) $html .= '<div class="aksrt-slide-text__desc">' . esc_html($desc) . '</div>';
|
||
$html .= '</div>';
|
||
}
|
||
|
||
$html .= '</div>';
|
||
}
|
||
|
||
$html .= '</div>'; // .swiper-wrapper
|
||
|
||
if ($args['show_nav']) {
|
||
$html .= '<div class="swiper-button-prev"></div>';
|
||
$html .= '<div class="swiper-button-next"></div>';
|
||
}
|
||
|
||
if ($args['show_pagination']) {
|
||
$html .= '<div class="swiper-pagination"></div>';
|
||
}
|
||
|
||
$html .= '</div>'; // .new-swiper
|
||
|
||
// Cover title/meta overlay (trusted HTML from single cover renderer).
|
||
if ((string) $args['overlay_html'] !== '') {
|
||
$html .= '<div class="aksrt-slider-overlay">' . $args['overlay_html'] . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||
}
|
||
|
||
$html .= '</div>'; // .aksrt-slider-wrapper
|
||
|
||
return $html;
|
||
}
|
||
|
||
/**
|
||
* 渲染水平滚动器(Tab 导航、横向内容列表)
|
||
*
|
||
* @param array $args {
|
||
* @type array $items 内容数组
|
||
* @type string $wrapper_class 额外 wrapper 类
|
||
* @type bool $autoplay 是否自动播放
|
||
* @type int $interval 自动播放间隔
|
||
* @type string $render_item 自定义渲染回调 (callable)
|
||
* }
|
||
* @return string HTML
|
||
*/
|
||
function aksrt_render_scroll_slider(array $args = []): string {
|
||
$defaults = [
|
||
'items' => [],
|
||
'wrapper_class' => '',
|
||
'autoplay' => false,
|
||
'interval' => 4000,
|
||
'render_item' => null,
|
||
];
|
||
|
||
$args = wp_parse_args($args, $defaults);
|
||
$items = $args['items'];
|
||
|
||
if (empty($items)) return '';
|
||
|
||
$data_attrs = ['class="swiper-scroll' . ($args['wrapper_class'] ? ' ' . esc_attr($args['wrapper_class']) : '') . '"'];
|
||
if ($args['autoplay']) {
|
||
$data_attrs[] = 'data-autoplay';
|
||
$data_attrs[] = 'data-interval="' . esc_attr((string) $args['interval']) . '"';
|
||
}
|
||
|
||
$html = '<div ' . implode(' ', $data_attrs) . '>';
|
||
$html .= '<div class="swiper-wrapper">';
|
||
|
||
foreach ($items as $i => $item) {
|
||
$html .= '<div class="swiper-slide">';
|
||
if (is_callable($args['render_item'])) {
|
||
$html .= call_user_func($args['render_item'], $item, $i);
|
||
} else {
|
||
$html .= '<div class="aksrt-scroll-item">' . esc_html($item['label'] ?? '') . '</div>';
|
||
}
|
||
$html .= '</div>';
|
||
}
|
||
|
||
$html .= '</div>';
|
||
$html .= '<div class="swiper-button-prev"></div>';
|
||
$html .= '<div class="swiper-button-next"></div>';
|
||
$html .= '</div>';
|
||
|
||
return $html;
|
||
}
|