惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog
GbyAI
GbyAI
Google DeepMind News
Google DeepMind News
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
N
Netflix TechBlog - Medium
P
Privacy International News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
G
Google Developers Blog
Recorded Future
Recorded Future
The Hacker News
The Hacker News
D
Darknet – Hacking Tools, Hacker News & Cyber Security
B
Blog RSS Feed
G
GRAHAM CLULEY
A
Arctic Wolf
N
News | PayPal Newsroom
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The Register - Security
The Register - Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
V
Visual Studio Blog
Webroot Blog
Webroot Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 三生石上(FineUI控件)
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
H
Heimdal Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Azure Blog
Microsoft Azure Blog
小众软件
小众软件
M
MIT News - Artificial intelligence
V2EX - 技术
V2EX - 技术
Jina AI
Jina AI
TaoSecurity Blog
TaoSecurity Blog
NISL@THU
NISL@THU
云风的 BLOG
云风的 BLOG
爱范儿
爱范儿
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
WordPress大学
WordPress大学
V
V2EX
Cyberwarzone
Cyberwarzone
Stack Overflow Blog
Stack Overflow Blog
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More

皮皮社

本地MP4 单集M3U8视频~ 这是流媒体视频~ Bilibili 视频~ 测试代码~简化版灯箱样式 测试一下图片自动转换WEBP功能~ Bdg怀旧主题 V3.52 更新记录 Bdg怀旧主题 V3.51 更新记录 H1-6 bilibili视频~ 大六宫格排版~ 大小宫格排版~ 九宫格排版~ 四宫格排版~ 三宫格 本地音频测试~ 视频集效果测试~ 时间线效果测试~ 折叠短代码测试~ 提示与按钮短代码~ 测试“回复与登陆”功能~ Typecho伪静态设置与主题自定义404页面跳转 欢迎使用 Typecho 大家都用什么AI大模型呢? 准备炒老板娘了! 准备炒老板娘了! 成本有点高~ 成本有点高~ 换了套表情~ 换了套表情~ 蛋蛋后应该不知道这什么游戏里的BGM吧~ 蛋蛋后应该不知道这什么游戏里的BGM吧~ 很喜欢这种调调的歌曲~ 很喜欢这种调调的歌曲~ 谢谢别人家的老婆跳舞给我看~ 谢谢别人家的老婆跳舞给我看~ [推荐] MediaHuman音频转换器! [推荐] MediaHuman音频转换器! [分享] 一款简单易用的MP3编辑器~ [分享] 一款简单易用的MP3编辑器~ 想她了~ 想她了~ 遮天大帝~ 你们手机有这个功能吗?怪好玩的! 国产浏览器真恶心🤢人! 拍了只鸟! 几年不见你还是那样明艳动人~
Typecho 在线图片转换压缩工具源码分享
作者: 柏哥 日期: 1小时前 · 2026-06-29 · via 皮皮社
<?php 
/**
 * 图片转换工具 3.0
 * @作者 皮皮社长
 * @package custom
 */
if (!defined('__TYPECHO_ROOT_DIR__')) exit; 
$this->setPingback(false);
$this->need('header.php');
?>

<?php $this->need('sidebar.php'); ?>
<div class="baige-mainContent">
    <div class="baige-innermainContent">
        <section class="tool-hero">
            <h2>在线图片转换压缩</h2>
            <p class="tool-desc-text">支持 JPG / PNG / WEBP 互转,自由调节画质,全部在浏览器本地完成</p>
        </section>

        <div class="tool-card img-tool">
            <div class="img-preview-wrap">
                <canvas id="previewCanvas"></canvas>
            </div>

            <div class="img-controls">
                <div class="img-selects">
                    <select class="tool-select" id="qualitySelect">
                        <option value="0.3" selected>画质:30% (小体积)</option>
                        <option value="0.7">画质:70% (平衡)</option>
                        <option value="1.0">画质:100% (无损)</option>
                    </select>
                    <select class="tool-select" id="formatSelect">
                        <option value="webp" selected>输出格式:WEBP</option>
                        <option value="jpg">输出格式:JPG</option>
                        <option value="png">输出格式:PNG</option>
                    </select>
                </div>
                <div class="img-actions">
                    <label class="tool-btn">
                        <input type="file" id="imageLoader" accept="image/jpg,image/jpeg,image/png,image/webp">
                        <span>选择图片</span>
                    </label>
                    <button class="tool-btn tool-btn-success" id="downloadButton">下载图片</button>
                </div>
            </div>

            <div class="img-tips">
                提示:1. PNG输出为无损格式,画质设置仅对JPG/WEBP生效;2. 百分比越高画质越好,文件越大;3. 支持JPG/PNG/WEBP格式互转
            </div>
        </div>

<br>
        <div class="baige-Content-bottom">
            <div>
                评论: <?php $this->commentsNum('0', '1', '%d'); ?> | 
                查看: <?php echo xm::get_post_view($this); ?>
            </div>
        </div>

        <?php $this->need('comments.php'); ?>
    </div>
</div>

<div class="res-toast" id="resToast"></div>
<?php $this->need('footer.php'); ?>

<script>
let currentImage = null, canvas = null, ctx = null, hasValidContent = false;
let imageSrc = '', originalFileName = 'converted', currentQuality = 0.3;
let currentFormat = 'webp', originalFileFormat = '';
const ALLOWED_TYPES = ['image/jpg','image/jpeg','image/png','image/webp'];
const FORMAT_MIME_TYPES = { jpg:'image/jpeg', png:'image/png', webp:'image/webp' };
const FORMAT_SUFFIX = { 'image/jpg':'jpg', 'image/jpeg':'jpg', 'image/png':'png', 'image/webp':'webp' };

function initCanvas() {
    canvas = document.getElementById('previewCanvas');
    ctx = canvas.getContext('2d');

    let savedData = null;
    if (hasValidContent && canvas.width > 0 && canvas.height > 0) {
        savedData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    }

    const size = Math.min(400, canvas.clientWidth);
    canvas.width = size;
    canvas.height = size;

    if (savedData) {
        ctx.putImageData(savedData, 0, 0);
    } else if (hasValidContent && currentImage) {
        previewImage();
    } else {
        let cs = getComputedStyle(document.documentElement);
        ctx.font = '16px Arial';
        ctx.fillStyle = cs.getPropertyValue('--color-chunhei');
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText('请上传图片', canvas.width / 2, canvas.height / 2);
    }
}

function previewImage() {
    if (!currentImage || !imageSrc) { initCanvas(); return; }
    let img = new Image();
    img.onload = function() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        let scale = Math.min(canvas.width / img.width, canvas.height / img.height);
        let w = img.width * scale;
        let h = img.height * scale;
        let x = (canvas.width - w) / 2;
        let y = (canvas.height - h) / 2;
        ctx.drawImage(img, x, y, w, h);
        hasValidContent = true;
    };
    img.src = imageSrc;
}

function handleImageUpload(e) {
    let file = e.target.files[0];
    if (!file) return;
    if (!ALLOWED_TYPES.includes(file.type)) {
        alert('仅支持 JPG / PNG / WEBP');
        e.target.value = '';
        return;
    }
    originalFileFormat = FORMAT_SUFFIX[file.type];
    originalFileName = file.name.split('.').slice(0, -1).join('.').replace(/[^\w\u4e00-\u9fa5]/g, '_');
    let reader = new FileReader();
    reader.onload = function(ev) {
        imageSrc = ev.target.result;
        currentImage = new Image();
        currentImage.src = imageSrc;
        currentImage.onload = previewImage;
    };
    reader.readAsDataURL(file);
}

function switchQuality(v) {
    currentQuality = parseFloat(v);
}

function switchFormat(f) {
    currentFormat = f;
}

function downloadImage() {
    if (!hasValidContent || !currentImage) {
        alert('请先上传图片');
        return;
    }
    let c = document.createElement('canvas');
    c.width = currentImage.width;
    c.height = currentImage.height;
    let ct = c.getContext('2d');
    ct.drawImage(currentImage, 0, 0);
    let mime = FORMAT_MIME_TYPES[currentFormat];
    let name = `${originalFileName}_${Date.now()}.${currentFormat}`;
    let q = currentQuality;
    if (q == 1) {
        if (currentFormat === 'webp') q = 0.95;
        if (currentFormat === 'jpg') q = 0.92;
    }
    c.toBlob(blob => {
        let url = URL.createObjectURL(blob);
        let a = document.createElement('a');
        a.href = url;
        a.download = name;
        a.click();
        URL.revokeObjectURL(url);
    }, mime, q);
}

window.addEventListener('DOMContentLoaded', () => {
    initCanvas();
    window.addEventListener('resize', initCanvas);
    document.getElementById('imageLoader').addEventListener('change', handleImageUpload);
    document.getElementById('qualitySelect').addEventListener('change', e => switchQuality(e.target.value));
    document.getElementById('formatSelect').addEventListener('change', e => switchFormat(e.target.value));
    document.getElementById('downloadButton').onclick = downloadImage;
});
</script>
/* 代码块处理:语法高亮 + 左侧行号 + 复制按钮 */
!function() {
  // ========== HTML 安全工具 ==========

  // 判断内容是否已经被 HTML 转义过
  function isEncoded(html) {
    return /&(?:lt|gt|amp|quot|#\d+|#x[\da-fA-F]+);/.test(html);
  }

  // 已转义文本的正则别名
  var LT = '&lt;', GT = '&gt;', AMP = '&amp;', QUOT = '&quot;';
  var LT_RE = '&(?:lt|#60);', GT_RE = '&(?:gt|#62);', AMP_RE = '&(?:amp|#38);';

  // 未转义文本用于模式匹配
  function getPatternSource() {
    return {
      lt: '<', gt: '>', amp: '&', quot: '"',
      ltOrGt: '[<>]', ws: '[\\s\\S]'
    };
  }

  // ========== 语法高亮规则(纯文本匹配) ==========

  // PHP 规则
  var patternsPHP = [
    {type: 'comment',   re: /\/\/.*|\/\*[\s\S]*?\*\/|#(?!\[)[^\n]*/},
    {type: 'string',    re: /`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'variable',  re: /\$[a-zA-Z_]\w*/},
    {type: 'keyword',   re: /\b(?:function|return|if|else|elseif|for|foreach|while|class|public|static|var|let|const|echo|print|new|this|self|parent|true|false|null|undefined|async|await|import|export|default|extends|super|try|catch|throw|break|continue|case|switch|do|in|of|typeof|instanceof|delete|void|yield|namespace|use|trait|interface|implements|final|abstract|protected|private|global|goto|declare|require|include|require_once|include_once|isset|unset|array|list|callable|mixed|int|float|bool|string|object|iterable|enum|match|fn|as|or|and|xor|die|exit|eval)\b/},
    {type: 'builtin',   re: /\b(?:strlen|substr|strpos|str_replace|strtolower|strtoupper|trim|explode|implode|in_array|array_push|array_merge|json_encode|json_decode|preg_match|preg_replace|preg_split|strtotime|date|time|mktime|count|sort|array_keys|array_values|empty|var_dump|print_r|htmlspecialchars|nl2br|basename|dirname|file_exists|is_array|is_string|is_numeric|intval|floatval|strval|abs|max|min|round|floor|ceil|sprintf|printf|pathinfo|parse_url|http_build_query|curl_init|curl_exec|curl_close|mysqli_connect|mysqli_query|mysqli_fetch_assoc|header|setcookie|session_start|session_destroy|md5|sha1|password_hash|password_verify|base64_encode|base64_decode|chmod|mkdir|unlink|file_get_contents|file_put_contents|fopen|fread|fwrite|fclose|sizeof|compact|extract)\b/},
    {type: 'number',    re: /\b(?:0x[\da-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b/},
    {type: 'function',  re: /\b[a-zA-Z_]\w*(?=\s*\()/},
    {type: 'operator',  re: /=>|[+\-*\/%]=|===|!==|==|!=|<=|>=|<<|>>|&&|\|\||[+\-*\/%=<>!&|^~?:]/},
  ];

  // CSS 规则
  var patternsCSS = [
    {type: 'comment',   re: /\/\*[\s\S]*?\*\//},
    {type: 'string',    re: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'important', re: /!important/},
    {type: 'selector',  re: /[.#@][a-zA-Z_-][\w-]*(?=[\s,{])/},
    {type: 'property',  re: /\b[a-z-]+(?=\s*:)/},
    {type: 'number',    re: /\b\d+(?:\.\d+)?(?:px|em|rem|%|s|ms|vh|vw|ex|ch|cm|mm|in|pt|pc|deg|rad|grad|turn|Hz|kHz|dpi|dpcm|dppx)?\b/},
    {type: 'keyword',   re: /@(?:import|media|keyframes|font-face|charset|namespace|supports|layer)/},
    {type: 'color',     re: /#[0-9a-fA-F]{3,8}\b/},
    {type: 'operator',  re: /[{}();,:>+~*]/},
  ];

  // HTML 规则
  var patternsHTML = [
    {type: 'comment',   re: /<!--[\s\S]*?-->/},
    {type: 'doctype',   re: /<!DOCTYPE[^>]*>/i},
    {type: 'tag',       re: /<\/?[a-zA-Z][\w-]*(?:\s[^>]*)?>/},
    {type: 'string',    re: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'attribute', re: /\b[a-zA-Z_][\w-]*(?=\s*=\s*["'])/},
  ];

  // JavaScript 规则
  var patternsJS = [
    {type: 'comment',   re: /\/\/.*|\/\*[\s\S]*?\*\//},
    {type: 'string',    re: /`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'keyword',   re: /\b(?:function|return|if|else|for|while|class|public|static|var|let|const|new|this|true|false|null|undefined|async|await|import|export|default|extends|super|try|catch|throw|break|continue|case|switch|do|in|of|typeof|instanceof|delete|void|yield|from|as|with|finally|debugger|arguments)\b/},
    {type: 'builtin',   re: /\b(?:console|document|window|Math|Array|Object|String|Number|Boolean|Date|RegExp|Error|Promise|JSON|parseInt|parseFloat|isNaN|isFinite|encodeURI|decodeURI|encodeURIComponent|decodeURIComponent|setTimeout|setInterval|clearTimeout|clearInterval|requestAnimationFrame|fetch|alert|confirm|prompt|addEventListener|removeEventListener|querySelector|querySelectorAll|getElementById|getElementsByClassName|getElementsByTagName|createElement|appendChild|removeChild|setAttribute|getAttribute|classList|style|textContent|innerHTML|value|length|push|pop|shift|unshift|splice|slice|concat|join|map|filter|reduce|forEach|find|indexOf|includes|keys|values|entries|assign|freeze|define|require|module|exports|process|Buffer|__dirname|__filename)\b/},
    {type: 'number',    re: /\b(?:0x[\da-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b/},
    {type: 'function',  re: /\b[a-zA-Z_$]\w*(?=\s*\()/},
    {type: 'operator',  re: /=>|[+\-*\/%]=|===|!==|==|!=|<=|>=|<<|>>|&&|\|\||[+\-*\/%=<>!&|^~?:]/},
  ];

  // Shell/Bash 规则
  var patternsBash = [
    {type: 'comment',   re: /#(?!\[)[^\n]*/},
    {type: 'string',    re: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'keyword',   re: /\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|in|function|return|exit|local|export|source|alias|unset|readonly|set|shift|echo|printf|read|cd|ls|cp|mv|rm|mkdir|rmdir|chmod|chown|cat|grep|sed|awk|find|sort|uniq|wc|head|tail|cut|tr|tee|xargs|curl|wget|tar|gzip|ssh|scp|rsync|apt|yum|npm|pip|git|docker|make|bash|sh|true|false|test)\b/},
    {type: 'variable',  re: /\$[a-zA-Z_]\w*|\$\{[^}]+\}/},
    {type: 'number',    re: /\b\d+(?:\.\d+)?\b/},
    {type: 'operator',  re: /[|&;<>!`=+\-*\/]/},
  ];

  // Nginx 规则
  var patternsNginx = [
    {type: 'comment',   re: /#(?!\[)[^\n]*/},
    {type: 'string',    re: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'keyword',   re: /\b(?:server|location|listen|server_name|root|index|rewrite|return|if|set|proxy_pass|proxy_set_header|try_files|alias|include|error_page|ssl_certificate|ssl_certificate_key|ssl_protocols|access_log|error_log|expires|add_header|gzip|gzip_types|fastcgi_pass|fastcgi_param|client_max_body_size|sendfile|tcp_nopush|tcp_nodelay|keepalive_timeout|autoindex|deny|allow|limit_conn|limit_req|upstream|map|log_format|on|off|permanent|redirect|last|break)\b/},
    {type: 'number',    re: /\b\d+(?:\.\d+)?(?:[smhd]|ms)?\b/},
    {type: 'variable',  re: /\$[a-zA-Z_]\w*/},
    {type: 'operator',  re: /[{}();|&;<>~]/},
  ];

  // SQL 规则
  var patternsSQL = [
    {type: 'comment',   re: /--(?!\[)[^\n]*|\/\*[\s\S]*?\*\//},
    {type: 'string',    re: /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/},
    {type: 'keyword',   re: /\b(?:SELECT|FROM|WHERE|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|ALTER|DROP|INDEX|UNIQUE|PRIMARY|KEY|FOREIGN|REFERENCES|ON|AND|OR|NOT|IN|EXISTS|LIKE|BETWEEN|IS|NULL|AS|JOIN|LEFT|RIGHT|INNER|OUTER|FULL|CROSS|GROUP|BY|ORDER|ASC|DESC|HAVING|LIMIT|OFFSET|UNION|ALL|DISTINCT|CASE|WHEN|THEN|ELSE|END|COUNT|SUM|AVG|MAX|MIN|IFNULL|COALESCE|CAST|CONVERT|CONCAT|SUBSTRING|TRIM|LENGTH|UPPER|LOWER|NOW|DATE|YEAR|MONTH|DAY|INT|VARCHAR|TEXT|FLOAT|DOUBLE|DECIMAL|BOOLEAN|SERIAL|AUTO_INCREMENT|ENGINE|CHARSET|COLLATE|GRANT|REVOKE|TRUNCATE|BEGIN|COMMIT|ROLLBACK|TRANSACTION|VIEW|TRIGGER|PROCEDURE|FUNCTION|RETURN|DECLARE|DEFAULT|TRUE|FALSE|SHOW|DESCRIBE|EXPLAIN|USE|DATABASE|SCHEMA)\b/i},
    {type: 'number',    re: /\b\d+(?:\.\d+)?\b/},
    {type: 'function',  re: /\b[a-zA-Z_]\w*(?=\s*\()/},
    {type: 'operator',  re: /[<>!=]=?|[+\-*\/%&|^~]/},
  ];

  // 通用规则
  var patternsGeneric = [
    {type: 'comment',   re: /\/\/.*|\/\*[\s\S]*?\*\/|#(?!\[)[^\n]*/},
    {type: 'string',    re: /`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/},
    {type: 'keyword',   re: /\b(?:function|return|if|else|for|while|class|public|static|var|let|const|echo|print|new|this|true|false|null|undefined|async|await|import|export|default|extends|super|try|catch|throw|break|continue|case|switch|do|in|of|typeof|instanceof|delete|void|yield|namespace|use|trait|interface|implements|final|abstract|protected|private)\b/},
    {type: 'variable',  re: /\$[a-zA-Z_]\w*/},
    {type: 'number',    re: /\b(?:0x[\da-fA-F]+|\d+(?:\.\d+)?)\b/},
    {type: 'function',  re: /\b[a-zA-Z_]\w*(?=\s*\()/},
    {type: 'operator',  re: /=>|[+\-*\/%]=|===|!==|==|!=|<=|>=|<<|>>|&&|\|\||[+\-*\/%=<>!&|^~?:]/},
  ];

  function getPatterns(lang) {
    var l = (lang || '').toLowerCase();
    if (l === 'php') return patternsPHP;
    if (l === 'css' || l === 'scss' || l === 'sass' || l === 'less') return patternsCSS;
    if (l === 'html' || l === 'xml' || l === 'markup') return patternsHTML;
    if (l === 'javascript' || l === 'js') return patternsJS;
    if (l === 'bash' || l === 'sh' || l === 'shell' || l === 'zsh') return patternsBash;
    if (l === 'nginx' || l === 'conf') return patternsNginx;
    if (l === 'sql' || l === 'mysql' || l === 'sqlite') return patternsSQL;
    return patternsGeneric;
  }

  // ========== Tokenizer(纯文本模式) ==========
  function tokenize(text, patterns) {
    var tokens = [];
    var pos = 0;
    var safety = 0;
    var maxIter = text.length * 3 + 100;
    while (pos < text.length && safety < maxIter) {
      safety++;
      var matched = false;
      for (var i = 0; i < patterns.length; i++) {
        var p = patterns[i];
        var re = new RegExp(p.re.source, 'y');
        re.lastIndex = pos;
        var m = re.exec(text);
        if (m && m[0].length > 0) {
          tokens.push({type: p.type, value: m[0]});
          pos += m[0].length;
          matched = true;
          break;
        }
      }
      if (!matched) {
        tokens.push({type: 'text', value: text[pos]});
        pos++;
      }
    }
    return tokens;
  }

  // 渲染 token 为 HTML(对纯文本做 HTML 转义)
  function encodeHtml(str) {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  function renderTokens(tokens) {
    var out = '';
    for (var i = 0; i < tokens.length; i++) {
      var t = tokens[i];
      var escaped = encodeHtml(t.value);
      if (t.type === 'text') {
        out += escaped;
      } else {
        out += '<span class="token ' + t.type + '">' + escaped + '</span>';
      }
    }
    return out;
  }

  function highlightLine(text, lang) {
    if (!text) return '';
    var patterns = getPatterns(lang);
    var tokens = tokenize(text, patterns);
    return renderTokens(tokens);
  }

  // ========== 行号 + 高亮 + 复制 ==========
  function addLineNumbers() {
    var codes = document.querySelectorAll('pre code');
    codes.forEach(function(code) {
      // 关键:用 textContent 获取浏览器解码后的纯文本
      // 这样无论原始 innerHTML 是纯文本还是已转义的 HTML,都能正确处理
      var rawText = code.textContent || '';
      var lines = rawText.split('\n');
      if (lines.length > 1 && lines[lines.length - 1] === '') {
        lines.pop();
      }

      // 检测语言
      var lang = '';
      code.classList.forEach(function(c) {
        if (c.indexOf('language-') === 0) {
          lang = c.replace('language-', '');
        }
      });

      var html = '';
      for (var i = 0; i < lines.length; i++) {
        var lineNum = i + 1;
        var highlighted = highlightLine(lines[i], lang);
        html += '<span class="code-line"><span class="line-number">' + lineNum + '</span><span class="line-content">' + highlighted + '</span></span>';
      }

      code.innerHTML = html;
      code.classList.add('has-line-numbers');
    });
  }

  function addCopyButton() {
    var pres = document.querySelectorAll('pre');
    pres.forEach(function(pre) {
      var code = pre.querySelector('code');
      if (!code) return;
      if (pre.querySelector('.code-copy-btn')) return;

      var btn = document.createElement('div');
      btn.className = 'code-copy-btn';
      btn.textContent = '复制';
      pre.appendChild(btn);

      btn.addEventListener('click', function() {
        var contents = code.querySelectorAll('.line-content');
        var text = Array.from(contents).map(function(span) {
          return span.textContent;
        }).join('\n');
        navigator.clipboard.writeText(text).then(function() {
          btn.textContent = '复制成功';
          setTimeout(function() { btn.textContent = '复制'; }, 3000);
        });
      });
    });
  }

  document.addEventListener('DOMContentLoaded', function() {
    addLineNumbers();
    addCopyButton();
  });
}();

保存为prism.js

/* 🔥 自动换行 + 代码字体缩小 + 复制按钮永久显示 */
pre code {
  display: block !important;
  font-family: Consolas, Monaco, monospace !important;
  font-size: 12px !important;
  line-height: 1.6 !important;
  color: var(--color-chunhei, #000) !important;

  white-space: pre-wrap !important;
  word-wrap: break-word !important;
  overflow-x: hidden !important;

  word-break: keep-all !important;
  tab-size: 4 !important;
  text-align: left !important;

  background-color: rgba(136, 173, 166, 0.15) !important;
  border: 1px solid var(--bj-shuise, #88ADA6) !important;
  border-radius: 4px !important;
  
  padding: 1em 1.2em !important;
  max-height: 300px !important;
  overflow-y: auto !important;
  overflow-x: auto !important;
}

pre {
  margin: 1em 0 !important;
  background: transparent !important;
  border: none !important;
  position: relative !important;
}

/* 复制按钮 —— 永久显示 */
.code-copy-btn {
  position: absolute !important;
  top: 6px !important;
  right: 10px !important;
  background: rgba(136, 173, 166, 0.2) !important;
  border: 1px solid var(--bj-shuise, #88ADA6) !important;
  border-radius: 3px !important;
  padding: 3px 6px !important;
  font-size: 12px !important;
  cursor: pointer !important;
  color: #333 !important;
  user-select: none !important;
  z-index: 99 !important;
  line-height: 1.2 !important;
  height: auto !important;
  
  /* 永久显示 */
  opacity: 1 !important;
  visibility: visible !important;
  transform: none !important;
  transition: none !important;
}

.code-copy-btn:hover {
  background: rgba(136, 173, 166, 0.3) !important;
}

/* 行内代码 */
:not(pre) > code {
  display: inline !important;
  padding: 0.2em 0.4em !important;
  background: rgba(136, 173, 166, 0.15) !important;
  border: 1px solid var(--bj-shuise, #88ADA6) !important;
  border-radius: 3px !important;
  font-family: Consolas, monospace !important;
  color: var(--color-chunhei, #000) !important;
}

/* 滚动条 永远不变色 */
pre code::-webkit-scrollbar {
  width: 8px !important;
  height: 8px !important;
}
pre code::-webkit-scrollbar-thumb {
  background: #ccc !important;
  border-radius: 4px !important;
}
pre code::-webkit-scrollbar-thumb:hover {
  background: #ccc !important;
}
pre code::-webkit-scrollbar-track {
  background: transparent !important;
}

/* ======================== */
/* 代码块左侧行号样式        */
/* ======================== */

/* 代码容器:取消默认 pre-wrap,由每行单独控制 */
pre code.has-line-numbers,
pre code {
  padding: 0.6em 0 !important;
  white-space: normal !important;
}

/* 每一行 */
.code-line {
  display: flex !important;
  align-items: stretch !important; /* 关键:让行号和代码列等高 */
  line-height: 1.7 !important;
  min-height: 1.7em !important;
}

/* 行号列 */
.line-number {
  display: flex !important;
  align-items: center !important; /* 垂直居中 */
  justify-content: flex-end !important;
  flex-shrink: 0 !important;
  min-width: 3em !important;
  padding: 0 0.8em 0 0.6em !important;
  text-align: right !important;
  color: #888 !important;
  background-color: rgba(136, 173, 166, 0.08) !important;
  border-right: 1px solid rgba(136, 173, 166, 0.3) !important;
  user-select: none !important;
  font-family: Consolas, Monaco, monospace !important;
  font-size: 12px !important;
}

/* 代码内容列 */
.line-content {
  display: block !important;
  flex: 1 !important;
  padding: 0 1em !important;
  white-space: pre-wrap !important;
  word-wrap: break-word !important;
  word-break: keep-all !important;
  font-family: Consolas, Monaco, monospace !important;
  font-size: 12px !important;
}

/* ======================== */
/* 语法高亮颜色(VS Code 浅色主题) */
/* ======================== */

/* 注释 - 绿色斜体 */
pre code .token.comment { color: #6a9955 !important; font-style: italic !important; }

/* 字符串 - 红棕色 */
pre code .token.string { color: #a31515 !important; }

/* 数字 - 绿色 */
pre code .token.number { color: #098658 !important; }

/* 关键字 - 蓝色加粗 */
pre code .token.keyword { color: #0000ff !important; font-weight: bold !important; }

/* 内置函数/对象 - 青色 */
pre code .token.builtin { color: #267f99 !important; }

/* 自定义函数 - 棕色 */
pre code .token.function { color: #795e26 !important; }

/* 变量 ($xxx) - 深蓝色 */
pre code .token.variable { color: #001080 !important; }

/* 运算符 - 黑色 */
pre code .token.operator { color: #000000 !important; }

/* HTML/XML 标签 - 栗色 */
pre code .token.tag { color: #800000 !important; }

/* HTML 属性名 - 红色 */
pre code .token.attribute { color: #e50000 !important; }

/* CSS 选择器 - 栗色 */
pre code .token.selector { color: #800000 !important; }

/* CSS 属性 - 红色 */
pre code .token.property { color: #e50000 !important; }

/* !important - 蓝色加粗 */
pre code .token.important { color: #0000ff !important; font-weight: bold !important; }

/* DOCTYPE - 蓝色 */
pre code .token.doctype { color: #0000ff !important; }

/* CSS 颜色值 - 紫色 */
pre code .token.color { color: #795e26 !important; }

保存为prism.css


平时写博客、做站点经常需要处理图片,在线工具广告多、还会上传原图泄露素材,于是自己写了一套 Typecho 页面模板,纯前端实现图片压缩与格式互转,无需后端接口,浏览器本地完成渲染转换,隐私更安全。
这套工具支持 JPG、PNG、WEBP 三种格式互相转换,内置 3 档画质调节,30% 轻量化压缩、70% 均衡画质、100% 高清无损按需选择。PNG 为无损输出,画质参数仅对 JPG、WEBP 生效,上传后画布实时预览图片,操作简单直观。
源码适配 Typecho 自定义页面,引入现有主题头部、侧边栏、评论模板,兼容主流 Typecho 主题。核心逻辑基于 Canvas 实现图片绘制、重绘与导出,读取本地文件不经过服务器,加载速度快,无图片上传流量消耗。
使用方法很简单:新建 Typecho 独立页面,粘贴全部代码,保存后即可访问使用。上传图片一键切换输出格式与压缩比例,点击直接下载处理后的文件,文件名自动重命名避免冲突。
工具轻量化无冗余代码,无第三方依赖,可自行拓展批量上传、尺寸裁剪等功能,适合博客站长、自媒体日常快速处理配图,完整源码附在文末,直接复制部署就能用。