在没开始折腾博客之前,我的时间基本上就是刷短视频,尤其是“美女”那一类的内容。大家都懂的,那个“刷刷刷”就停不下来,完全不在意什么点赞和收藏,反正看一遍就过了,哪里还记得有什么印象深刻的。然而最近当我再刷“美女”的时候,却怎么也找不到之前刷过的那些觉得不错的妹子了!真是让人心碎啊,脑袋里仿佛空空如也。
在我投入更多精力给博客之后,我觉得可以做点“有意义”的事情了。既然主题折腾得差不多,干脆再加个类似“媒体库”的功能吧。这样我就可以把那些让我心动的图片或短视频收藏起来,随时翻翻,看看自己到底是个啥口味儿。如果有人问我博客里怎么会放这些“图片”或“视频”。我可以大方地回答:“这可是我收藏的艺术品,是她们充实了我的灵魂!”
"养眼"功能的实现有:
1.后台增加一个自定义文章类型“收藏”,功能很简单只需要在后台添加标题,从媒体库中选择图片(图片组)或视频。点击发布后即可显示在前端“养眼”选项卡中。
2.界面布局:使用 CSS Grid 布局
- PC端: 3列布局
- 平板: 2列布局
- 手机: 1列布局
- 每个项目包含媒体内容和标题
3.功能特性
图片功能- 点击图片可以打开灯箱查看大图 - 支持一组多图滑动浏览。
视频功能- PC 端: 点击视频弹出灯箱播放 - 移动端: 直接在列表中播放。
分页加载 - 每页显示 12 条内容 - 滚动到底部自动加载下一页 - 加载时显示简易 loading 动画。
为完善这些功能的细节,陆陆续续折腾了有大半天时间。
page-notes.php 文件里的代码翻来覆去改了很多次,就不记录了。其它代码记录如下:
functions.php 文件中增加如下代码实现后端功能支持:
// 收藏MM function register_beauty_post_type() { register_post_type('beauty', array( 'labels' => array( 'name' => '收藏', 'singular_name' => '收藏', 'add_new' => '添加新内容', 'add_new_item' => '添加新内容', 'edit_item' => '编辑内容', 'new_item' => '新内容', 'view_item' => '查看内容', 'search_items' => '搜索内容', 'not_found' => '没有找到内容', 'not_found_in_trash' => '回收站里没有内容', 'menu_name' => '收藏' ), 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'beauty'), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 5, 'supports' => array('title'), 'menu_icon' => 'dashicons-heart' )); } add_action('init', 'register_beauty_post_type'); function enqueue_media_uploader() { global $post_type; if( 'beauty' == $post_type ) { wp_enqueue_media(); } } add_action('admin_enqueue_scripts', 'enqueue_media_uploader'); function add_beauty_meta_boxes() { add_meta_box( 'beauty_meta_box', '收藏内容信息', 'render_beauty_meta_box', 'beauty', 'normal', 'high' ); } add_action('add_meta_boxes', 'add_beauty_meta_boxes'); function render_beauty_meta_box($post) { wp_nonce_field('save_beauty_meta', 'beauty_meta_nonce'); $media_type = get_post_meta($post->ID, 'media_type', true); $images = get_post_meta($post->ID, 'beauty_images', false); ?> <div class="beauty-meta-box"> <p> <label><input type="radio" name="media_type" value="image" <?php checked($media_type, 'image'); ?>> 图片</label> <label><input type="radio" name="media_type" value="video" <?php checked($media_type, 'video'); ?>> 视频</label> </p> <div id="image_section" style="<?php echo $media_type !== 'image' ? 'display:none' : ''; ?>"> <p> <button type="button" class="button" id="select_images">选择图片</button> </p> <input type="hidden" id="beauty_images" name="beauty_images" value=""> <div id="image_preview" class="image-preview-grid"> <?php if(!empty($images)): ?> <?php foreach($images as $index => $image): ?> <div class="preview-item <?php echo $index === 0 ? 'main-image' : ''; ?>"> <img src="<?php echo esc_url($image); ?>" alt=""> <?php if($index === 0): ?> <span class="badge">主图</span> <?php endif; ?> </div> <?php endforeach; ?> <?php endif; ?> </div> </div> <div id="video_section" style="<?php echo $media_type !== 'video' ? 'display:none' : ''; ?>"> <p> <input type="text" id="video_url" name="video_url" value="<?php echo esc_attr(get_post_meta($post->ID, 'video_url', true)); ?>" class="large-text"> <button type="button" class="button" id="select_video">选择视频</button> </p> </div> </div> <style> .image-preview-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 10px; margin-top: 10px; } .preview-item { position: relative; width: 100%; padding-top: 100%; } .preview-item img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } .main-image { border: 2px solid #2271b1; } .badge { position: absolute; top: 5px; left: 5px; background: #2271b1; color: white; padding: 2px 5px; font-size: 12px; border-radius: 3px; } </style> <script> jQuery(document).ready(function($) { var currentImages = <?php echo wp_json_encode($images); ?>; if(currentImages && currentImages.length) { $('#beauty_images').val(JSON.stringify(currentImages)); } $('input[name="media_type"]').change(function() { var type = $(this).val(); $('#image_section').toggle(type === 'image'); $('#video_section').toggle(type === 'video'); }); $('#select_images').click(function(e) { e.preventDefault(); var mediaUploader = wp.media({ title: '选择图片', button: { text: '选择' }, multiple: true, library: { type: 'image' } }); mediaUploader.on('select', function() { var selection = mediaUploader.state().get('selection'); if(selection.length === 0) return; var images = []; var preview = ''; selection.each(function(attachment, index) { var url = attachment.toJSON().url; images.push(url); preview += '<div class="preview-item ' + (index === 0 ? 'main-image' : '') + '">' + '<img src="' + url + '" alt="">' + (index === 0 ? '<span class="badge">主图</span>' : '') + '</div>'; }); $('#beauty_images').val(JSON.stringify(images)); $('#image_preview').html(preview); }); mediaUploader.open(); }); $('#select_video').click(function(e) { e.preventDefault(); var mediaUploader = wp.media({ title: '选择视频', button: { text: '选择' }, multiple: false, library: { type: 'video' } }); mediaUploader.on('select', function() { var attachment = mediaUploader.state().get('selection').first().toJSON(); $('#video_url').val(attachment.url); }); mediaUploader.open(); }); }); </script> <?php } function save_beauty_meta($post_id) { if (!isset($_POST['beauty_meta_nonce']) || !wp_verify_nonce($_POST['beauty_meta_nonce'], 'save_beauty_meta')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!current_user_can('edit_post', $post_id)) { return; } if (isset($_POST['media_type'])) { update_post_meta($post_id, 'media_type', sanitize_text_field($_POST['media_type'])); } if (isset($_POST['beauty_images'])) { $images = json_decode(wp_unslash($_POST['beauty_images'])); if(is_array($images)) { delete_post_meta($post_id, 'beauty_images'); foreach($images as $url) { add_post_meta($post_id, 'beauty_images', esc_url_raw($url)); } } } if (isset($_POST['video_url'])) { update_post_meta($post_id, 'video_url', esc_url_raw($_POST['video_url'])); } } add_action('save_post_beauty', 'save_beauty_meta');
简易 css 样式记录如下:
.beauty-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; padding: 0.5rem 0; } .beauty-item { background: var(--bg-color); border-radius: 0.2rem; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .image-container { width: 100%; padding-top: 75%; position: relative; overflow: hidden; } .image-container img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } @media (max-width: 900px) { .beauty-grid { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 600px) { .beauty-grid { grid-template-columns: repeat(1, 1fr); } } .beauty-title { padding: 10px; margin: 0; font-size: .7rem; text-align: center; color: var(--text-color); } .video-container { width: 100%; padding-top: 75%; position: relative; } .video-preview { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: block; overflow: hidden; cursor: pointer; } .video-preview video { width: 100%; height: 100%; object-fit: cover; object-position: center; } .play-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.3); display: flex; align-items: center; justify-content: center; transition: background 0.3s ease; } .play-overlay i { font-size: 3rem; color: white; opacity: 0.8; transition: opacity 0.3s ease; } .gallery-container { width: 100%; height: 100%; cursor: pointer; } .fancybox__content { padding: 0 !important; } @media (max-width: 768px) { .video-preview video { object-fit: contain; background: #000; } }