联系我们
🚀 2026新春限定 比比工房5重豪礼来袭!现金红包+免费插件+高价值抽奖,速进群抢福利!活动详情 →
点赞达人 Lv.1
生财有道 Lv.2
家财万贯 Lv.3
荣耀宾客 Lv.1
名列前茅 Lv.1
挥金如土 Lv.2 <!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>红薯笔记生成器</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'MiSans', sans-serif;
background-color: #f4f5f7;
display: flex;
flex-direction: row;
height: 100vh;
overflow: hidden;
}
.pane {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
overflow: auto;
}
#editorPane {
background: #fff;
border-right: 1px solid #eee;
}
#editorPane h2, #previewPane h2 {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
}
#editorPane textarea {
flex: 1;
resize: none;
padding: 12px;
font-size: 14px;
line-height: 1.6;
border: 1px solid #ddd;
border-radius: 8px;
width: 100%;
min-height: 200px;
background-color: #fafafa;
}
.mode-container.desktop-only {
display: block;
}
.mode-grid {
display: flex;
flex-direction: row;
gap: 12px;
overflow-x: auto;
padding: 12px 0;
scroll-snap-type: x mandatory;
white-space: nowrap;
}
.mode-card {
flex: 0 0 auto;
scroll-snap-align: start;
display: inline-block;
min-width: 120px;
max-width: 160px;
cursor: pointer;
border: 2px solid transparent;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 1px 5px rgba(0,0,0,0.05);
text-align: center;
transition: all 0.2s ease;
padding: 14px 10px;
font-size: 14px;
font-weight: 500;
color: #333;
user-select: none;
}
.mode-card.active {
border: 2px solid #007aff;
background-color: #e6f0ff;
color: #007aff;
}
.controls {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.controls input, .controls button, .controls select {
padding: 8px 12px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 6px;
background: #fff;
outline: none;
transition: all 0.2s ease;
}
.controls button {
background-color: #007aff;
color: #fff;
border: none;
cursor: pointer;
}
.controls button:hover {
background-color: #005fcc;
}
#previewPane {
background-color: #f9fafb;
}
#previewContainer {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
}
#previewContainer img {
width: 360px;
height: 480px;
object-fit: cover;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
canvas.hidden-canvas {
display: none;
}
.desktop-only { display: block; }
.mobile-only { display: none; }
@media (max-width: 768px) {
body { flex-direction: column; }
#editorPane, #previewPane {
flex: none;
height: auto;
max-height: 50%;
}
#previewContainer img {
width: 90vw;
height: auto;
}
.desktop-only { display: none; }
.mobile-only {
display: inline-block;
min-width: 120px;
max-width: 100%;
flex: 1;
}
}
</style>
</head>
<body>
<div id="editorPane" class="pane">
<h2>编辑文案 (粘贴)</h2>
<textarea id="markdownInput" placeholder="在此输入文案..."></textarea>
<div class="mode-container desktop-only">
<div class="mode-grid" id="modeGrid">
<div class="mode-card active" data-mode="light">白色备忘录</div>
<div class="mode-card" data-mode="dark">黑色备忘录</div>
<div class="mode-card" data-mode="bg1">新背景1</div>
<div class="mode-card" data-mode="bg2">新背景2</div>
<div class="mode-card" data-mode="bg3">新背景3</div>
<div class="mode-card" data-mode="bg4">新背景4</div>
<div class="mode-card" data-mode="bg5">新背景5</div>
<div class="mode-card" data-mode="bg6">新背景6</div>
</div>
</div>
<div class="controls">
<select id="modeSelect" class="mobile-only">
<option value="light">白色备忘录</option>
<option value="dark">黑色备忘录</option>
<option value="bg1">新背景1</option>
<option value="bg2">新背景2</option>
<option value="bg3">新背景3</option>
<option value="bg4">新背景4</option>
<option value="bg5">新背景5</option>
<option value="bg6">新背景6</option>
</select>
<input id="titleSize" type="number" min="20" max="80" value="80" title="标题字号" />
<button id="genBtn">生成图片</button>
<button id="downloadBtn">下载全部</button>
</div>
</div>
<div id="previewPane" class="pane">
<h2>生成预览</h2>
<div id="previewContainer"></div>
<canvas id="canvas" width="1080" height="1440" class="hidden-canvas"></canvas>
</div>
<script src="img/marked.min.js"></script>
<script>
const bgImages = {
light: 'img/bg1.webp',
dark: 'img/bg2.webp',
bg1: 'img/bg3.webp',
bg2: 'img/bg4.webp',
bg3: 'img/bg5.webp',
bg4: 'img/bg6.webp',
bg5: 'img/bg7.webp',
bg6: 'img/bg8.webp'
};
function autoWrapText(ctx, text, maxW) {
const paras = text.split(/\n|\r\n/), lines = [];
paras.forEach(para => {
let line = '';
[...para].forEach(ch => {
line += ch;
if (ctx.measureText(line).width > maxW) {
lines.push(line.slice(0, -1));
line = ch;
}
});
if (line) lines.push(line);
});
return lines;
}
function titleLinesHeight(lines, size) {
return lines.length * (size + 10);
}
function render(pages, titleLines, titleSize, fontColor, backgroundImage) {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const preview = document.getElementById('previewContainer');
preview.innerHTML = '';
const pw = 1080, ph = 1440, mTop = 200, mBottom = 50, tGap = 40, lh = 70, pL = 60;
pages.forEach((lines, i) => {
ctx.clearRect(0, 0, pw, ph);
ctx.drawImage(backgroundImage, 0, 0, pw, ph);
ctx.fillStyle = fontColor;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
let y = mTop;
if (i === 0 && titleLines.length) {
ctx.font = `bold ${titleSize}px MiSans`;
titleLines.forEach(l => {
ctx.fillText(l, pL, y);
y += titleSize + 10;
});
y += tGap;
}
ctx.font = `42px MiSans`;
lines.forEach(l => {
ctx.fillText(l, pL, y);
y += lh;
});
const img = new Image();
img.src = canvas.toDataURL();
preview.appendChild(img);
});
}
function getActiveMode() {
const modeCard = document.querySelector('.mode-card.active');
if (modeCard) return modeCard.dataset.mode;
return document.getElementById('modeSelect').value;
}
function generateImages() {
const md = document.getElementById('markdownInput').value;
const lines = md.split(/\n/);
const title = lines[0].replace(/^#+\s*/, '') || '';
const contentMd = lines.slice(1).join('\n');
const titleSize = +document.getElementById('titleSize').value;
const mode = getActiveMode();
const fontColor = ['dark'].includes(mode) ? '#fff' : '#333';
const bgUrl = bgImages[mode];
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const pw = 1080, ph = 1440, mTop = 200, mBottom = 50, tGap = 40, lh = 70, cW = 960, pL = 60;
ctx.font = `42px MiSans`;
const plain = contentMd.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\*(.*?)\*/g, '$1');
const contentLines = autoWrapText(ctx, plain, cW);
let titleLines = [];
if (title) {
ctx.font = `bold ${titleSize}px MiSans`;
titleLines = autoWrapText(ctx, title, cW);
}
const firstCnt = Math.floor((ph - mTop - mBottom - titleLinesHeight(titleLines, titleSize) - tGap) / lh);
const otherCnt = Math.floor((ph - mTop - mBottom) / lh);
let pages = [];
if (contentLines.length <= firstCnt) {
pages = [contentLines];
} else {
pages = [contentLines.slice(0, firstCnt)];
let rem = contentLines.slice(firstCnt);
while (rem.length) {
pages.push(rem.slice(0, otherCnt));
rem = rem.slice(otherCnt);
}
}
const bg = new Image();
bg.crossOrigin = 'anonymous';
bg.src = bgUrl;
bg.onload = () => render(pages, titleLines, titleSize, fontColor, bg);
if (bg.complete) render(pages, titleLines, titleSize, fontColor, bg);
}
function downloadAllImages() {
document.querySelectorAll('#previewContainer img').forEach((img, i) => {
const a = document.createElement('a');
a.href = img.src;
a.download = `memo_${i + 1}.png`;
a.click();
});
}
document.querySelectorAll('.mode-card').forEach(card => {
card.addEventListener('click', () => {
document.querySelectorAll('.mode-card').forEach(c => c.classList.remove('active'));
card.classList.add('active');
document.getElementById('modeSelect').value = card.dataset.mode;
generateImages();
});
});
document.getElementById('modeSelect').addEventListener('change', () => {
const val = document.getElementById('modeSelect').value;
document.querySelectorAll('.mode-card').forEach(c => {
c.classList.toggle('active', c.dataset.mode === val);
});
generateImages();
});
document.getElementById("genBtn").onclick = generateImages;
document.getElementById("downloadBtn").onclick = downloadAllImages;
</script>
</body>
</html>
排名不分先后
“”
奖励已发放至卡包中