Files
kate522 d86147f406 fix(single): fill slide and video cover media like the image cover
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
2026-09-05 18:53:45 +08:00

240 lines
9.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}