

























<?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 = '<', GT = '>', AMP = '&', 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, '&').replace(/</g, '<').replace(/>/g, '>');
}
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 独立页面,粘贴全部代码,保存后即可访问使用。上传图片一键切换输出格式与压缩比例,点击直接下载处理后的文件,文件名自动重命名避免冲突。
工具轻量化无冗余代码,无第三方依赖,可自行拓展批量上传、尺寸裁剪等功能,适合博客站长、自媒体日常快速处理配图,完整源码附在文末,直接复制部署就能用。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。