Vibe Coding 半小时成果:无缝衔接 AI 模型生成的「信息卡」网页,一键实现网页展示和截图导出

大模型向量数据库图像处理

咱们分享信息卡提示词后,收到了很多朋友的建议和咨询,其中很集中的一个问题是:Gemini 3、Grok 4.1 和 Kimi K2 这些 AI 模型生成 HTML 内容后,怎么截图呢?

其实我之前是手动贴到浏览器做的预览和手动截图,因为有时还想自己微调一些地方。而在咱们的预览效果稳定后,1:1 完全复刻截图,就可行了,今天咱们就把这个支持「粘贴和编辑 HTML 内容、HTML 预览、截图导出图片、支持背景和留白」功能的 HTML 网页贴在下面。

Vibe Coding 半小时的成果,为了让非软件开发背景的朋友们都能用,都能自己动手做,这回 Vibe Coding 咱们没用 Cursor 和 Claude Code 这种开发工具,而是直接在 Gemini App、Grok App 这种 AI 工具里通过 Prompt 来完成的,大家可以用 Kimi、Qwen、ChatGPT 等任意 AI 助手来做自己的 HTML 功能。

开始前咱们再回顾一下信息卡提示词系列:

适配「公众号/小红书」等平台的「多张知识信息卡」提示词

醒目杂志风信息/知识卡提示词(手机友好)

提示词分享:用 AI 模型给文章生成信息卡片(Gemini 3 效果最佳、Kimi K2 最听话稳定)

废话不多说,朋友们直接复制下方 HTML 内容,保存为一个 HTML 后缀的文件,再双击在浏览器中打开,就可以用了。

HTML 效果:

picture.image

HTML 源码:

  
<!DOCTYPE html>  
<html lang="zh-CN">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <title>Mistral Studio V13 - 零空白终极版</title>  
    <script src="https://cdn.tailwindcss.com"></script>  
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>  
    <style>  
        body, html { height: 100%; margin: 0; overflow: hidden; background: #09090b; color: #fff; font-family: -apple-system, sans-serif; }  
        .app-layout { display: flex; height: 100vh; width: 100vw; }  
        .editor-col {  
            width: 420px; background: #18181b; border-right: 1px solid #27272a;  
            display: flex; flex-direction: column; transition: all 0.3s; z-index: 50; flex-shrink: 0;  
        }  
        .editor-col.closed { width: 0; border: none; overflow: hidden; padding: 0; }  
        .editor-header {  
            height: 50px; background: #27272a; border-bottom: 1px solid #3f3f46;  
            display: flex; align-items: center; justify-content: space-between; padding: 0 16px;  
        }  
        textarea {  
            flex: 1; background: #18181b; color: #e4e4e7; border: none; padding: 20px;  
            font-family: 'JetBrains Mono', monospace; font-size: 13.5px; resize: none; outline: none; line-height: 1.6;  
        }  
        .preview-col { flex: 1; display: flex; flex-direction: column; background: #09090b; }  
        .toolbar {  
            height: 60px; background: #18181b; border-bottom: 1px solid #27272a;  
            display: flex; align-items: center; justify-content: space-between; padding: 0 24px;  
        }  
        .stage {  
            flex: 1; overflow: auto; display: flex; justify-content: center; align-items: flex-start;  
            padding: 60px; background-color: #dcd9d0;  
            background-image: radial-gradient(rgba(0,0,0,0.1) 1px, transparent 1px);  
            background-size: 20px 20px;  
        }  
        .preview-iframe-wrap {  
            width: 100%; max-width: 800px; background: white;   
            box-shadow: 0 20px 40px -10px rgba(0,0,0,0.3); border-radius: 8px; overflow: hidden;  
        }  
        iframe { width: 100%; height: 100%; border: none; display: block; background: #fff; }  
        .float-btn {  
            position: absolute; left: 20px; top: 80px; width: 44px; height: 44px; z-index: 100;  
            background: #27272a; border: 1px solid #3f3f46; color: #fff; border-radius: 10px;  
            display: none; align-items: center; justify-content: center; cursor: pointer; font-size: 18px;  
        }  
        .btn-primary {   
            background: #b83b18; color: white; padding: 10px 24px; border-radius: 8px;   
            font-size: 14px; font-weight: 600; display: flex; gap: 8px; align-items: center; cursor: pointer;  
        }  
        .btn-primary:hover { background: #d9461e; }  
        .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }  
        .setting-group { display: flex; align-items: center; gap: 20px; font-size: 13.5px; color: #d4d4d4; }  
    </style>  
</head>  
<body>  
<div class="app-layout">  
    <div id="floatBtn" class="float-btn" onclick="toggleSidebar()">Code</div>  
    <div class="editor-col" id="editorCol">  
        <div class="editor-header">  
            <span class="font-bold text-gray-300">HTML SOURCE</span>  
            <div class="flex gap-2">  
                <button class="btn-icon" onclick="render()" title="刷新预览">Refresh</button>  
                <button class="btn-icon" onclick="toggleSidebar()">Collapse</button>  
            </div>  
        </div>  
        <textarea id="htmlInput" spellcheck="false"></textarea>  
    </div>  
    <div class="preview-col">  
        <div class="toolbar">  
            <div class="setting-group">  
                <label class="flex items-center gap-2 cursor-pointer select-none">  
                    <input type="checkbox" id="paddingCheck" checked class="accent-orange-600 w-4 h-4 rounded">  
                    <span>背景留白 + 投影</span>  
                </label>  
                <div class="h-5 w-[1px] bg-zinc-600"></div>  
                <div class="flex items-center gap-2">  
                    <input type="color" id="bgColor" value="#dcd9d0">  
                    <span class="text-xs font-mono">背景色</span>  
                </div>  
            </div>  
            <button class="btn-primary" onclick="captureEngine()">  
                Export HD Image 导出高清图片  
            </button>  
        </div>  
        <div class="stage" id="stage">  
            <div class="preview-iframe-wrap">  
                <iframe id="previewFrame" sandbox="allow-scripts allow-same-origin"></iframe>  
            </div>  
        </div>  
    </div>  
</div>  
<script>  
    const htmlInput = document.getElementById('htmlInput');  
    const previewFrame = document.getElementById('previewFrame');  
    const stage = document.getElementById('stage');  
    const bgColorInput = document.getElementById('bgColor');  
    const paddingCheck = document.getElementById('paddingCheck');  
    const editorCol = document.getElementById('editorCol');  
    const floatBtn = document.getElementById('floatBtn');  
    const defaultCode = `<!DOCTYPE html>  
<html lang="zh-CN">  
<head>  
<meta charset="UTF-8">  
<style>  
    @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@900&family=Inter:wght@400;700&display=swap');  
    body { margin: 0; padding: 60px; background: transparent; font-family: 'Inter', sans-serif; }  
    .card {  
        max-width: 640px; margin: 0 auto; background: white; padding: 60px; border: 4px solid #000;  
        box-shadow: 20px 20px 0 rgba(0,0,0,0.1); border-radius: 16px;  
    }  
    h1 { font-family: 'Noto Serif SC', serif; font-size: 5rem; margin: 0 0 30px 0; line-height: 1; color: #1a1a1a; }  
    .box { height: 600px; background: linear-gradient(135deg, #ff9a9e, #fad0c4);   
           border: 5px dashed #e11d48; border-radius: 20px; margin: 50px 0;  
           display: flex; align-items: center; justify-content: center;  
           font-size: 2.5rem; font-weight: bold; color: #881337; }  
    .end { height: 300px; background: #1e293b; color: white; border-radius: 20px;  
           display: flex; align-items: center; justify-content: center; font-size: 3rem; }  
</style>  
</head>  
<body>  
<div class="card">  
    <h1>完美贴合<br>零空白</h1>  
    <p>现在底部再也没有多余白边了!</p>  
    <div class="box">600px 测试块<br>完整显示</div>  
    <div class="end">内容到底了!</div>  
</div>  
</body>  
</html>`;  
    window.onload = () => {  
        htmlInput.value = defaultCode;  
        render();  
        updateBg();  
    };  
    function toggleSidebar() {  
        editorCol.classList.toggle('closed');  
        floatBtn.style.display = editorCol.classList.contains('closed') ? 'flex' : 'none';  
    }  
    htmlInput.addEventListener('keydown', e => { if (e.ctrlKey && e.key === 'Enter') render(); });  
    bgColorInput.addEventListener('input', updateBg);  
    function updateBg() { stage.style.background = bgColorInput.value; }  
    function render() {  
        previewFrame.srcdoc = htmlInput.value;  
        previewFrame.onload = () => {  
            setTimeout(() => {  
                const doc = previewFrame.contentDocument;  
                const h = Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight);  
                previewFrame.style.height = h + 'px';  
            }, 400);  
        };  
    }  
    // V13 终极零空白截图引擎  
    async function captureEngine() {  
        const btn = document.querySelector('.btn-primary');  
        const oldText = btn.innerHTML;  
        btn.innerHTML = 'Export HD Image 渲染中...';  
        btn.disabled = true;  
        try {  
            const iframe = previewFrame;  
            const doc = iframe.contentDocument || iframe.contentWindow.document;  
            if (!doc) throw new Error("iframe 未加载");  
            // 等待字体加载  
            if (doc.fonts && doc.fonts.ready) await doc.fonts.ready;  
            const body = doc.body;  
            const html = doc.documentElement;  
            // 关键:计算真实内容高度(不加任何保险)  
            const realHeight = Math.max(  
                body.scrollHeight,  
                body.offsetHeight,  
                html.scrollHeight,  
                html.offsetHeight  
            );  
            // 为了防止 1px 误差,只把容器撑开一点点(不影响最终截图)  
            const safeHeight = realHeight + 40;  
            iframe.style.height = safeHeight + 'px';  
            body.style.minHeight = safeHeight + 'px';  
            html.style.height = safeHeight + 'px';  
            body.style.overflow = 'visible';  
            await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));  
            // 注入 html2canvas(如果还没)  
            if (!iframe.contentWindow.html2canvas) {  
                const script = doc.createElement('script');  
                script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';  
                doc.head.appendChild(script);  
                await new Promise(r => script.onload = r);  
            }  
            // 核心:传给 html2canvas 的必须是 realHeight!!  
            const canvas = await iframe.contentWindow.html2canvas(doc.documentElement, {  
                scale: 2,  
                useCORS: true,  
                allowTaint: true,  
                backgroundColor: null,  
                logging: false,  
                scrollX: 0,  
                scrollY: 0,  
                width: html.scrollWidth,  
                height: realHeight,           // 精确高度  
                windowWidth: html.scrollWidth,  
                windowHeight: realHeight,     // 精确高度  
                foreignObjectRendering: true,  
                onclone: (cloned) => {  
                    cloned.body.style.minHeight = realHeight + 'px';  
                    cloned.documentElement.style.height = realHeight + 'px';  
                }  
            });  
            // 加背景留白与投影(可选)  
            let finalCanvas = canvas;  
            if (paddingCheck.checked) {  
                const padding = 120;  
                finalCanvas = document.createElement('canvas');  
                finalCanvas.width = canvas.width + padding * 2;  
                finalCanvas.height = canvas.height + padding * 2;  
                const ctx = finalCanvas.getContext('2d');  
                ctx.fillStyle = bgColorInput.value;  
                ctx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);  
                ctx.shadowColor = "rgba(0,0,0,0.25)";  
                ctx.shadowBlur = 70;  
                ctx.shadowOffsetY = 35;  
                ctx.drawImage(canvas, padding, padding);  
            }  
            const a = document.createElement('a');  
            a.download = `perfect-${Date.now()}.png`;  
            a.href = finalCanvas.toDataURL('image/png');  
            a.click();  
        } catch (e) {  
            console.error(e);  
            alert("截图失败:" + e.message);  
        } finally {  
            btn.innerHTML = oldText;  
            btn.disabled = false;  
        }  
    }  
</script>  
</body>  
</html>

picture.image

.cls-1{fill:

#001e36

;}.cls-2{fill:

#31a8ff

;}

.cls-1{fill:

#001e36

;}.cls-2{fill:

#31a8ff

;}

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论