WordPress Custom Code: 不用 Plugin, 自己写 Function!

为什么我最终选择了WordPress

寻找写博客的CMS一段时间后,发现还是用WordPress来部署自己的Blog最合适。虽然市面上有各式各样的静态网站生成器、轻量级CMS,但WordPress凭借其强大的生态系统、灵活的定制性和成熟的社区支持,依然是个人Blog的不二之选。

我给自己定了个规矩:能自己动手写的功能,就尽量不用插件。这样不仅能精确控制网站性能,还能逼着自己多学点WordPress的开发知识,一举两得!

接下来我就把自己开发Blog时写的代码分享出来,希望能帮到也在折腾WordPress的你。

1.  JPG/PNG自动转换WebP格式

为什么要搞图片优化?

先来看看WebP这个格式有多香:

  • 无损压缩:比PNG小26%,画质一模一样
  • 有损压缩:在同等质量下,比JPEG小25-34%
  • 动图也行:支持动画,比GIF和APNG小得多

说白了,WebP就是现在网页图片的首选格式,能在保证画质的前提下,把图片体积压缩到最小。

图片太大到底有多烦?

你想想看:

  • 打开一个网页,图片刷半天出不来
  • 手机流量刷刷的掉
  • Google一看你网站慢,直接把你排名往后挪

所以这个问题必须解决!

我的方法:自动转换+删掉原始文件

效果:

Original Image: JPG format, 2.62mb

当Upload 完成后:Webp format, 1mb

方法:

  1. 把下面这段代码扔进 wp-content\themes\your-theme\functions.php 文件里就行:
/**
 * Auto-convert uploaded images to WebP and DELETE originals
 */
// Hook into WordPress upload process
add_filter('wp_handle_upload', 'convert_to_webp_and_delete_original');

function convert_to_webp_and_delete_original($upload_data) {
    
    $file_path = $upload_data['file'];
    $file_type = $upload_data['type'];
    $supported_types = ['image/jpeg', 'image/png'];
    
    // Only process supported image types
    if (!in_array($file_type, $supported_types)) {
        return $upload_data;
    }
    
    // Check if GD or Imagick supports WebP
    if (!function_exists('imagewebp') && !class_exists('Imagick')) {
        // error_log('No WebP support detected');
        return $upload_data;
    }
    
    // Create image resource based on file type
    switch ($file_type) {
        case 'image/jpeg':
            $image = imagecreatefromjpeg($file_path);
            break;
        case 'image/png':
            $image = imagecreatefrompng($file_path);
            // Preserve transparency
            imagepalettetotruecolor($image);
            imagealphablending($image, true);
            imagesavealpha($image, true);
            break;
        default:
            return $upload_data;
    }
    
    if (!$image) {
        return $upload_data;
    }
    
    // Define WebP path (same name, different extension)
    $webp_path = preg_replace('/\.(jpe?g|png)$/i', '.webp', $file_path);
    
    // Convert to WebP (quality: 80)
    $success = imagewebp($image, $webp_path, 80);
    imagedestroy($image);
    
    if ($success) {
        // Delete the original JPG/PNG file
        unlink($file_path);
        
        // Update the upload data to point to the WebP file
        $upload_data['file'] = $webp_path;
        $upload_data['url'] = preg_replace('/\.(jpe?g|png)$/i', '.webp', $upload_data['url']);
        $upload_data['type'] = 'image/webp';
    }
    
    return $upload_data;
}

// Also need to update attachment metadata
add_filter('wp_generate_attachment_metadata', 'update_metadata_for_webp', 10, 2);

function update_metadata_for_webp($metadata, $attachment_id) {
    $attachment = get_post($attachment_id);
    $file_path = get_attached_file($attachment_id);
    
    // If this is a WebP file (original was deleted), make sure metadata reflects that
    if (pathinfo($file_path, PATHINFO_EXTENSION) === 'webp') {
        $metadata['mime_type'] = 'image/webp';
        
        // Update file reference in metadata
        if (isset($metadata['file'])) {
            $metadata['file'] = preg_replace('/\.(jpe?g|png)$/i', '.webp', $metadata['file']);
        }
    }
    
    return $metadata;
}

2. 图片点击放大功能

写blog难免会配上图片,然而default 的图片不能够被点击然后放大。

所以我写了一个功能, 当检测是图片的时候,点击出现lightbox, 然后图片就被放大了。

效果如下:

点击前:

点击后:

使用方法:

  1. 把以下code 放在 wp-content\themes\your-theme\inc\lightbox.php
<?php
add_filter('the_content', 'add_simple_lightbox');

function add_simple_lightbox($content) {
    // Only run on single posts
    if (!is_single()) {
        return $content;
    }
    
    // Add lightbox class to all images
    $content = preg_replace_callback(
        '/<a href="([^"]+\.(jpg|jpeg|png|gif|webp))"[^>]*><img([^>]*)>/i',
        function($matches) {
            return '<a href="' . $matches[1] . '" class="simple-lightbox-trigger"><img' . $matches[3] . '></a>';
        },
        $content
    );
    
    // Add CSS and JS (only once)
    static $lightbox_added = false;
    if (!$lightbox_added) {
        $lightbox_added = true;
        
        $content .= '<style>
            /* Lightbox CSS - copy all the CSS from previous code */
            .simple-lightbox-modal {
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.9);
                z-index: 999999;
                cursor: pointer;
            }
            
            .simple-lightbox-modal.active {
                display: flex;
                align-items: center;
                justify-content: center;
            }
            
            .simple-lightbox-modal img {
                max-width: 90%;
                max-height: 90%;
                object-fit: contain;
                box-shadow: 0 0 20px rgba(0,0,0,0.5);
            }
            
            .lightbox-close {
                position: absolute;
                top: 20px;
                right: 30px;
                color: white;
                font-size: 40px;
                font-weight: bold;
                cursor: pointer;
                z-index: 9999999;
            }
            
            .lightbox-close:hover {
                color: #ccc;
            }
            
            .lightbox-prev, .lightbox-next {
                position: absolute;
                top: 50%;
                transform: translateY(-50%);
                color: white;
                font-size: 50px;
                font-weight: bold;
                cursor: pointer;
                padding: 20px;
                z-index: 9999999;
                user-select: none;
            }
            
            .lightbox-prev { left: 20px; }
            .lightbox-next { right: 20px; }
            
            .lightbox-prev:hover, .lightbox-next:hover {
                color: #ccc;
            }
            
            .lightbox-counter {
                position: absolute;
                bottom: 20px;
                left: 50%;
                transform: translateX(-50%);
                color: white;
                font-size: 14px;
                background: rgba(0,0,0,0.5);
                padding: 5px 15px;
                border-radius: 20px;
            }
            
            /* Image hover effect */
            .simple-lightbox-trigger {
                display: inline-block;
                position: relative;
                cursor: pointer;
            }
            
            .simple-lightbox-trigger:hover:after {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                font-size: 30px;
                background: rgba(0,0,0,0.5);
                color: white;
                padding: 10px;
                border-radius: 50%;
                pointer-events: none;
            }
        </style>';
        
        $content .= '<script>
            document.addEventListener("DOMContentLoaded", function() {
                // Create lightbox elements
                const modal = document.createElement("div");
                modal.className = "simple-lightbox-modal";
                modal.innerHTML = `
                    <span class="lightbox-close">&times;</span>
                    <span class="lightbox-prev">&#10094;</span>
                    <span class="lightbox-next">&#10095;</span>
                    <img src="" alt="Enlarged image">
                    <div class="lightbox-counter"></div>
                `;
                document.body.appendChild(modal);
                
                const modalImg = modal.querySelector("img");
                const closeBtn = modal.querySelector(".lightbox-close");
                const prevBtn = modal.querySelector(".lightbox-prev");
                const nextBtn = modal.querySelector(".lightbox-next");
                const counter = modal.querySelector(".lightbox-counter");
                
                // Get all images
                const images = Array.from(document.querySelectorAll(".simple-lightbox-trigger"));
                let currentIndex = 0;
                
                // Open lightbox
                images.forEach((img, index) => {
                    img.addEventListener("click", function(e) {
                        e.preventDefault();
                        currentIndex = index;
                        showImage(currentIndex);
                        modal.classList.add("active");
                        document.body.style.overflow = "hidden";
                    });
                });
                
                // Show image function
                function showImage(index) {
                    const imgSrc = images[index].getAttribute("href");
                    modalImg.src = imgSrc;
                    counter.textContent = (index + 1) + " / " + images.length;
                }
                
                // Close lightbox
                function closeLightbox() {
                    modal.classList.remove("active");
                    document.body.style.overflow = "";
                }
                
                closeBtn.addEventListener("click", closeLightbox);
                modal.addEventListener("click", function(e) {
                    if (e.target === modal) {
                        closeLightbox();
                    }
                });
                
                // Keyboard navigation
                document.addEventListener("keydown", function(e) {
                    if (!modal.classList.contains("active")) return;
                    
                    if (e.key === "Escape") {
                        closeLightbox();
                    } else if (e.key === "ArrowLeft") {
                        e.preventDefault();
                        currentIndex = (currentIndex - 1 + images.length) % images.length;
                        showImage(currentIndex);
                    } else if (e.key === "ArrowRight") {
                        e.preventDefault();
                        currentIndex = (currentIndex + 1) % images.length;
                        showImage(currentIndex);
                    }
                });
                
                // Previous/Next buttons
                prevBtn.addEventListener("click", function(e) {
                    e.stopPropagation();
                    currentIndex = (currentIndex - 1 + images.length) % images.length;
                    showImage(currentIndex);
                });
                
                nextBtn.addEventListener("click", function(e) {
                    e.stopPropagation();
                    currentIndex = (currentIndex + 1) % images.length;
                    showImage(currentIndex);
                });
                
                // Touch swipe for mobile
                let touchStartX = 0;
                let touchEndX = 0;
                
                modal.addEventListener("touchstart", function(e) {
                    touchStartX = e.changedTouches[0].screenX;
                }, false);
                
                modal.addEventListener("touchend", function(e) {
                    touchEndX = e.changedTouches[0].screenX;
                    handleSwipe();
                }, false);
                
                function handleSwipe() {
                    if (touchEndX < touchStartX - 50) {
                        // Swipe left
                        currentIndex = (currentIndex + 1) % images.length;
                        showImage(currentIndex);
                    } else if (touchEndX > touchStartX + 50) {
                        // Swipe right
                        currentIndex = (currentIndex - 1 + images.length) % images.length;
                        showImage(currentIndex);
                    }
                }
            });
        </script>';
    }
    
    return $content;
}

// Also make sure images are wrapped in links
add_filter('the_content', 'ensure_images_have_links', 5);

function ensure_images_have_links($content) {
    // If image isn't wrapped in a link, wrap it
    $content = preg_replace_callback(
        '/<img([^>]*)src="([^"]+)"([^>]*)>/i',
        function($matches) {
            // Check if already in a link
            if (preg_match('/<a[^>]*>.*' . preg_quote($matches[0], '/') . '.*<\/a>/i', $matches[0])) {
                return $matches[0];
            }
            
            // Check if it's inside a link already
            $full_context = $matches[0];
            if (strpos($full_context, '</a>') !== false) {
                return $matches[0];
            }
            
            // Wrap in link
            return '<a href="' . $matches[2] . '" class="simple-lightbox-trigger">' . $matches[0] . '</a>';
        },
        $content
    );
    
    return $content;
}
?>
  1. 然后在 wp-content\themes\your-theme\functions.php 写下这段
// Add the image lightbox
require_once get_stylesheet_directory() . '/inc/lightbox.php';

3. Prettify Code

写技术博客嘛,肯定少不了贴代码。但你有没有发现,默认情况下WordPress显示代码真的很”朴素”——就是白底黑字,跟普通文本没啥两样。代码挤在一起,该有的颜色高亮全没有,看起来特别的累。

解决方案:Google Prettify

Google Prettify是一个lightweight library,不需要指定语言,它能自动识别。关键是体积小、速度快,完全满足我的需求。只是目前我还没调节好Copy and paste 的功能,或许将来我在优化这方面。

在这片blog 你看到的code 呈现方式都是Prettify 显示出来的

File Structure:

├── wp-content/
│   └── your-theme/
│       └── css/
|           └── prettify.css
│       └── js/
|           └── prettify.js

文件:

在Github 浏览

重点:

  1. 当创建完css 和 js folder,然后放了各自的css 和 js file, 记得在 wp-content\themes\your-theme\functions.php 写下这段
function add_prettify_to_posts() {
    if ( is_single() || is_page() ) {
        wp_enqueue_script( 
            'prettify-js', 
            get_template_directory_uri() . '/js/prettify/prettify.js', 
            array(), 
            'r298', 
            true 
        );
        
        wp_enqueue_style( 
            'prettify-css', 
            get_template_directory_uri() . '/css/prettify.css', 
            array(), 
            'r298' 
        );
    }
}
add_action( 'wp_enqueue_scripts', 'add_prettify_to_posts' );


function auto_add_prettyprint_to_pre($content) {
    if ( is_single() || is_page() ) {
        $content = preg_replace_callback(
            '/<pre(?![^>]*class=)([^>]*)>(.*?)<\/pre>/is',
            function($matches) {
                return '<pre class="prettyprint" ' . $matches[1] . '>' . $matches[2] . '</pre>';
            },
            $content
        );
    }
    return $content;
}
add_filter('the_content', 'auto_add_prettyprint_to_pre', 20);

function init_prettify() {
    if ( is_single() || is_page() ) {
        echo '<script>!function(){var e=document,t=e.createElement("script");t.src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js",e.body.appendChild(t)}();</script>';
    }
}
add_action('wp_footer', 'init_prettify');

2. 然后在你编写blog 的时候,在code 的additional CSS 里加 “prettyprint linenums” 就可以了

Leave a Reply

Your email address will not be published. Required fields are marked *