纯 CSS+JS 实战:手搓一张支持自定义的通用金色电子奖状(附 10 讲详细拆解)
无论是在学校给学生发"进步之星",还是给公司员工发"优秀员工",我们经常需要生成一张极具仪式感、且能随时修改内容的电子奖状。
如果每次换人都要重写 HTML,或者依赖外部图片导致网络不佳时图标裂开,这非常不专业。
今天,我将把这个项目拆成 10 个核心逻辑块,像老师教课一样,手把手带大家实现一个"数据与表现分离的通用电子奖状生成器".
代码特点:
- ✅ 零外部依赖:不依赖任何图片文件、外部图标库(钉钉图标纯手绘 SVG,绝不会有裂图风险)。
- ✅ 数据与视图分离:改为"学校、公司、个人"均可使用的通用模板。
- ✅ 完美还原纸质奖状质感:双线金边、官方仿宋体、羊皮纸背景。
- ✅ 响应式适配:在手机钉钉、微信中打开,排版依然完美。
第一讲:HTML 基础骨架 ------ 使用"占位符"而非固定死文字
很多开发者写奖状,直接把文字写死,导致后期替换极其痛苦。正确的做法是,把所有需要动态替换的部分加上 id,让 HTML 变成一个纯框架。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes">
<title>通用电子奖状生成器</title>
</head>
<body>
<div class="certificate-box">
<!-- 学校/公司名称 -->
<div class="school-title" id="js-school">[请填写学校/公司名称]</div>
<!-- 奖项名称 -->
<div class="award-title" id="js-award">[请填写奖项名称]</div>
<!-- 获奖人(分开占位,方便换称呼) -->
<div class="recipient">
<span id="js-name">[获奖人姓名]</span> <span id="js-title">同学/先生/女士</span>
</div>
<!-- 颁奖词 -->
<div class="content">
在 <strong id="js-event">[请填写考核/项目名称]</strong> 中,<br>
表现优异,进步显著。<br>
特发此状,以资鼓励!
</div>
<div class="footer-area">
<div class="dingtalk-wrapper">
<!-- 待会儿用纯 SVG 手绘一个钉钉图标 -->
<svg class="dingtalk-svg" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="16" fill="#0089FF" />
<path d="M28.06 14.04 L46.04 30.24 L34.7 32.24 L33.46 33.8 L43.28 44.36 L26.46 36.68 L13.58 48.84 L23.4 37.62 L16.46 34.56 L22.8 27.32 L28.06 14.04 Z" fill="white" />
</svg>
<span class="publish-text" id="js-source">来自钉钉</span>
</div>
<div class="date-wrap">
<div class="signature" id="js-sign">[请填写落款单位]</div>
<div class="date-text" id="js-date">[请填写日期]</div>
</div>
</div>
</div>
</body>
</html>
讲深一层: 这里的 ID(如 js-school、js-name)就是给 JavaScript 看的"锚点"。以后换人、换单位,我们绝对不会触碰这一段 HTML 代码,全部留给 JS 去操作,这体现了前端开发的"高内聚、低耦合"思想。

第二讲:建立数据配置中心 ------ 所有人名、单位全归它管
为了做到"公司、学校、个人都能用",我们把所有需要动态替换的内容,提炼成一个独立的 AWARD_DATA 对象。你不懂 JS 也没关系,你只需要看懂中文变量名即可。
javascript
// ============================================================
// 核心配置区(就在这里修改,奖状内容立刻变成你的!)
// 不管是给员工、学生,还是给导师发奖,改完这儿就行。
// ============================================================
const AWARD_DATA = {
schoolName:"", // 改成你的单位名称
awardTitle: "", // 改成你要发的奖项
recipientName: "", // 改成获奖人姓名
recipientTitle: "", // 改成"先生"、"女士"、"老师"等
eventName: "", // 改成具体的事件
publishSource: "", // 发奖平台(钉钉/微信/企业微信)
signature: "", // 落款单位
issueDate: "" // 落款日期
};
讲深一层: 这种"数据与视图分离"的做法,是开发大型项目的基石。哪怕是一个小小的奖状生成器,运用了这个思维,以后你要给 100 个人生成奖状,就只需要复制 100 次这段配置代码,改改姓名和日期即可,完全不用去抠 CSS 或 HTML 排版。
第三讲:渲染引擎 ------ 把数据精准注入到 HTML 框架中
光有数据没用,我们需要一个 JavaScript 的渲染函数。它就像快递员一样,把前面配置好的 AWARD_DATA 精准地塞进 HTML 的 id 占位符里。
javascript
// 渲染引擎:把数据加载到页面相应的位置
function renderCertificate(data) {
document.getElementById('js-school').innerText = data.schoolName;
document.getElementById('js-award').innerText = data.awardTitle;
document.getElementById('js-name').innerText = data.recipientName;
document.getElementById('js-title').innerText = data.recipientTitle;
document.getElementById('js-event').innerText = data.eventName;
document.getElementById('js-source').innerText = data.publishSource;
document.getElementById('js-sign').innerText = data.signature;
document.getElementById('js-date').innerText = data.issueDate;
}
// 页面加载完成后自动触发渲染
window.onload = function() {
renderCertificate(AWARD_DATA);
};
讲深一层: 我们使用了最基础的 document.getElementById().innerText。在"前端框架(如 Vue/React)"大行其道的今天,原生 JS 依然能极好地胜任这种静态模板填充工作,且没有任何加载依赖包的开销,不到 10 行代码解决了"动态换人"的问题。
第四讲:全局背景环境 ------ 奠定宣纸质感,凸显主体
从这一讲开始,我们正式进入 CSS 美工阶段。纯白背景会让奖状的白边"隐形"且没有质感。
css
body {
background: #f0e6d2; /* 浅牛皮纸色,模拟老式奖状底纹 */
margin: 0;
padding: 30px;
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
color: #4a3b1f; /* 深褐色文字,比纯黑更柔和,看着更高级 */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
box-sizing: border-box;
}
讲深一层: display: flex; justify-content: center; align-items: center; 是 CSS 最强的居中大法,无论屏幕多大,这个奖状永远像挂在墙上一样,完美居中。我们在四周留出了 30px 的呼吸空间,不会让边框顶到屏幕边缘。
第五讲:核心金边容器 ------ 拒绝图片,纯 CSS 画双线金边
很多框架实现双线金边需要用两张图堆叠,这在电子奖状里极度糟糕。我们只用一行 CSS 属性搞定。
css
.certificate-box {
width: 100%;
max-width: 680px;
background: #ffffff;
/* 灵魂代码:6px 宽度 + double 双线模式 + 金色 */
border: 6px double #d4af37;
border-radius: 6px;
padding: 50px 40px 30px 40px;
text-align: center;
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
}
讲深一层: border: 6px double #d4af37; 中的 double 是 CSS 原生支持的属性值。它能在 6px 的线段内自动生成 "外细线-空白-内细线" 的精密结构,和传统印刷的烫金工艺如出一辙。加上淡淡的 box-shadow,一张纸的厚度感跃然纸上。
效果图↓

第六讲:官方字体与排版 ------ 不使用卡通体
学校及企事业单位的奖状,通常使用"仿宋"或"楷体"。我们通过字体栈来优化不同电脑的显示效果。
css
.school-title {
font-family: "华文仿宋", "STFangsong", "仿宋", serif;
font-size: 26px; font-weight: bold; color: #b8860b; /* 深金色 */
letter-spacing: 4px;
display: inline-block; /* 控制下划线长度 */
border-bottom: 2px solid #e8d5a3;
padding-bottom: 10px;
}
.award-title {
font-family: "华文仿宋", "STFangsong", "仿宋", serif;
font-size: 42px; font-weight: 700; color: #d4af37;
letter-spacing: 6px;
text-shadow: 1px 1px 0px rgba(0,0,0,0.03);
}
讲深一层: 注意 .school-title 里的 display: inline-block;。如果没有这行代码,底部的金线会横跨整个奖状,非常突兀;加上这个属性,金线就会老老实实地缩在文字的正下方,这是排版印刷非常讲究的细节。
第七讲:姓名专属金线 ------ 凸显获奖人的荣誉感
奖状的名字底端,必须有一条细致的"修饰线"。我们利用 HTML 的 精准定位。
css
.recipient { font-size: 24px; font-weight: 700; letter-spacing: 2px; }
.recipient span {
border-bottom: 2px solid #d4af37;
padding: 0 4px;
}
讲深一层: 使用 padding: 0 4px; 能巧妙地让金线左右两端都比文字长出 4 像素,避免了金线紧贴文字的死板感。这些细微的缝隙正是纸质印刷品的魅力所在。
第八讲:无图化发布标志 ------ 纯 SVG 绘制钉钉图标
很多人用 标签引入钉钉图标,一旦目标机器断网,或者用浏览器打开本地文件,就会出现"图片裂开"。为了让奖状在任何网络环境下都能 100% 显示,我们手写一个钉钉图标的 SVG 代码。
css
.dingtalk-svg { width: 36px; height: 36px; }
(SVG 路径已在第一讲的 HTML 骨架中预埋。它由蓝色的圆角矩形和白色的翅膀路径组成,无需任何外部资源)。
讲深一层: SVG 是矢量图,字体小,放大 1000 倍也不会模糊,而且它是纯文本代码。把这个图标内嵌在 HTML 中,是一个奖状保持"科技感"和"普适性"的终极解决方案。
第九讲:移动端自适应 ------ 钉钉/微信里绝不乱码
很多奖状发到手机钉钉群里就变形了,我们必须用 @media 专门给手机做适配。
css
@media (max-width: 600px) {
body { padding: 15px; }
.certificate-box { padding: 30px 20px; }
.footer-area { flex-direction: column; align-items: center; gap: 20px; }
}
讲深一层: 当屏幕宽度小于 600px(手机标准)时,底部原来左右并排的"发布平台"和"落款",会通过 flex-direction: column; 变为垂直排列,并且全部居中。这样无论手机多窄,都不会出现内容被挤出屏幕的情况。
第十讲:实战应用演示 ------ 如何给公司、学校和个人生成奖状?
这一讲教你核心用法。我前面写的所有代码,你都不用管,你只需要进入 AWARD_DATA 配置区修改参数。
场景 1:给公司的优秀员工发奖
只需修改配置区:
javascript
const AWARD_DATA = {
schoolName: "XX科技股份有限公司",
awardTitle: "最佳员工奖",
recipientName: "王小明",
recipientTitle: "先生",
eventName: "2026年度Q3业绩冲刺",
publishSource: "来自企业微信",
signature: "XX科技股份有限公司",
issueDate: "2026年9月"
};
场景 2:给辅导班老师发感谢状
只需修改配置区:
javascript
const AWARD_DATA = {
schoolName: "某教育培训机构",
awardTitle: "优秀教师奖",
recipientName: "李红梅",
recipientTitle: "老师",
eventName: "2026年秋季教研成果展示",
publishSource: "来自钉钉",
signature: "某教育培训机构",
issueDate: "2026年8月"
};
配置好后,保存 HTML 文件在浏览器刷新,奖状上的文字就会瞬间变为你填的内容。这就是"配置驱动"带来的极高灵活性。
最终完整版开源代码(一键复制即可使用)
如果你不需要自己琢磨原理,只想在电脑里存一个随时能改的电子奖状,请直接复制下方完整的代码,保存为 award.html。双击打开,在顶部的 AWARD_DATA 里修改文字,你的奖状就做出来了。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes">
<title>商务通用 · 电子荣誉证书</title>
<style>
/* 背景:高质感米白底,模拟羊皮纸 */
body {
background: #e8e2d5;
margin: 0;
padding: 30px;
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
color: #3a2e1b;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
box-sizing: border-box;
}
/* ============================================================
极简入场动画:只有0.8s的纸张展开感
============================================================ */
@keyframes unfoldPaper {
0% { opacity: 0; transform: scale(0.96) translateY(15px); }
100% { opacity: 1; transform: scale(1) translateY(0); }
}
/* ============================================================
证书外框(外层金边 + 内层金边)
============================================================ */
.certificate-box {
width: 100%;
max-width: 780px;
background: #ffffff;
position: relative;
/* 外层双线金边 */
border: 5px double #d4af37;
border-radius: 10px;
padding: 0;
animation: unfoldPaper 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
box-shadow: 0 10px 35px rgba(0,0,0,0.08);
overflow: visible; /* 允许红丝带伸出 */
}
/* 内层细金线框(增强印刷定制感) */
.certificate-inner {
margin: 15px;
padding: 45px 50px 30px 50px;
border: 1px solid #d4af37;
border-radius: 5px;
position: relative;
background: #ffffff;
min-height: 400px;
}
/* ============================================================
顶部大红花结(纯 SVG 手绘)
============================================================ */
.ribbon-container {
position: absolute;
top: -32px;
left: 50%;
transform: translateX(-50%);
width: 130px;
height: 70px;
z-index: 10;
}
.ribbon-container svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 2px 6px rgba(0,0,0,0.15));
}
/* ============================================================
大印章(纯CSS绘制,加粗边框)
============================================================ */
.official-seal {
position: absolute;
bottom: 30px;
right: 35px;
width: 105px;
height: 105px;
border: 5px solid #b32d26;
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #b32d26;
background: rgba(255, 255, 255, 0.85); /* 印泥渗透感 */
z-index: 5;
transform: rotate(-12deg);
box-shadow: 0 0 15px rgba(179, 45, 38, 0.08);
opacity: 0.85;
}
.official-seal .star {
font-size: 28px;
line-height: 1;
margin-bottom: 2px;
font-weight: bold;
}
.official-seal .seal-text {
font-family: "华文楷体", "STKaiti", "楷体", serif;
font-size: 13px;
font-weight: bold;
line-height: 1.3;
text-align: center;
letter-spacing: 1px;
padding: 0 5px;
}
/* ============================================================
高级排版(全套楷体/仿宋,加粗金线)
============================================================ */
.org-title {
font-family: "华文楷体", "STKaiti", "楷体", serif;
font-size: 28px;
font-weight: bold;
color: #8b6508;
letter-spacing: 4px;
margin-bottom: 5px;
text-align: center;
}
.award-title {
font-family: "华文楷体", "STKaiti", "楷体", serif;
font-size: 42px;
font-weight: 700;
color: #b32d26; /* 证书专用深红色 */
letter-spacing: 8px;
margin: 10px 0 20px 0;
text-align: center;
text-shadow: 0.5px 0.5px 1px rgba(0,0,0,0.03);
}
.recipient-area {
font-size: 26px;
font-weight: bold;
color: #3a2e1b;
margin: 15px 0 25px 0;
text-align: center;
letter-spacing: 2px;
}
.recipient-area span {
border-bottom: 2px solid #d4af37;
padding: 0 6px;
font-family: "华文楷体", "STKaiti", "楷体", serif;
}
.content-area {
font-family: "华文楷体", "STKaiti", "楷体", serif;
font-size: 20px;
line-height: 2.2;
color: #3a2e1b;
margin: 0 0 40px 0;
text-align: center;
letter-spacing: 1.5px;
padding: 0 20px;
}
.content-area strong {
font-weight: bold;
}
/* 底部双栏布局(去掉钉钉后留白更显官方) */
.footer-area {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 10px;
padding-top: 15px;
border-top: 1px solid #d4af37;
}
.footer-left {
flex: 1;
font-size: 14px;
color: #888;
text-align: left;
letter-spacing: 1px;
}
.date-wrap {
text-align: right;
padding-right: 10px;
}
.signature {
font-family: "华文楷体", "STKaiti", "楷体", serif;
font-size: 20px;
color: #3a2e1b;
font-weight: 600;
margin-bottom: 4px;
letter-spacing: 4px;
}
.date-text {
font-size: 16px;
color: #555;
font-weight: 500;
letter-spacing: 1px;
}
/* 移动端适配 */
@media (max-width: 600px) {
body { padding: 15px; }
.certificate-inner { padding: 40px 20px 20px 20px; }
.org-title { font-size: 22px; letter-spacing: 3px; }
.award-title { font-size: 32px; letter-spacing: 6px; }
.recipient-area { font-size: 22px; }
.content-area { font-size: 18px; padding: 0; }
.official-seal { width: 80px; height: 80px; bottom: 15px; right: 15px; }
.official-seal .seal-text { font-size: 11px; }
.official-seal .star { font-size: 22px; }
.ribbon-container { width: 100px; height: 50px; top: -25px; }
.footer-area { flex-direction: column; align-items: flex-end; gap: 10px; }
.footer-left { text-align: right; width: 100%; }
}
</style>
</head>
<body>
<div class="certificate-box">
<div class="certificate-inner">
<!-- 顶部红丝带大花 -->
<div class="ribbon-container">
<svg viewBox="0 0 130 70" xmlns="http://www.w3.org/2000/svg">
<!-- 左右下飘带 -->
<path d="M20 30 C10 50, 15 65, 25 70 L50 50 C40 40, 25 35, 20 30 Z" fill="#b32d26" />
<path d="M110 30 C120 50, 115 65, 105 70 L80 50 C90 40, 105 35, 110 30 Z" fill="#b32d26" />
<!-- 上方主花瓣 -->
<path d="M30 25 C35 5, 95 5, 100 25 C105 45, 90 55, 65 60 C40 55, 25 45, 30 25 Z" fill="#d43d34" />
<!-- 下方辅花瓣 -->
<path d="M35 40 C40 65, 90 65, 95 40 C100 30, 80 20, 65 20 C50 20, 30 30, 35 40 Z" fill="#c22c24" />
<!-- 中心亮色花蕊 -->
<circle cx="65" cy="35" r="15" fill="#f17a74" />
<circle cx="65" cy="35" r="6" fill="#b32d26" />
</svg>
</div>
<!-- 单位/公司抬头 -->
<div class="org-title" id="js-org">XX单位</div>
<!-- 奖项名称 -->
<div class="award-title" id="js-award">荣誉证书</div>
<!-- 获奖人 -->
<div class="recipient-area">
<span id="js-name">获奖人</span> <span id="js-title">先生/女士</span>
</div>
<!-- 正文内容 -->
<div class="content-area">
在 <strong id="js-event">XX年度评优活动</strong> 中,<br>
表现优异,成绩突出。<br>
特发此证,以资鼓励!
</div>
<!-- 底部(去掉钉钉,完全官方留白) -->
<div class="footer-area">
<div class="footer-left">
<!-- 可以放一句官方声明,也可以空着,显得更干净 -->
</div>
<div class="date-wrap">
<div class="signature" id="js-sign">发证单位</div>
<div class="date-text" id="js-date">XXXX年XX月</div>
</div>
</div>
<!-- 右下角官方大印章 -->
<div class="official-seal">
<div class="star">★</div>
<div class="seal-text" id="js-seal">发证单位</div>
</div>
</div>
</div>
<script>
/* ============================================================
数据配置区(支持学校、公司、乙方表彰,改这里即可)
============================================================ */
const AWARD_DATA = {
orgName: "XX有限公司/学校", // 抬头机构
awardTitle: "优秀员工/进步奖", // 奖状名称
recipientName: "王小明", // 获奖人姓名
recipientTitle: "先生/女士/同学", // 称呼
awardEvent: "2026年Q3项目考核", // 具体事由
signature: "XX单位", // 落款机构
seal: "XX单位\n颁发", // 印章内容(支持 \n 换行)
issueDate: "2026年8月" // 日期
};
/* ============================================================
数据渲染引擎
============================================================ */
function renderCertificate(data) {
document.getElementById('js-org').innerText = data.orgName;
document.getElementById('js-award').innerText = data.awardTitle;
document.getElementById('js-name').innerText = data.recipientName;
document.getElementById('js-title').innerText = data.recipientTitle;
document.getElementById('js-event').innerText = data.awardEvent;
document.getElementById('js-sign').innerText = data.signature;
document.getElementById('js-seal').innerHTML = data.seal.replace('\n', '<br>');
document.getElementById('js-date').innerText = data.issueDate;
}
window.onload = function() {
renderCertificate(AWARD_DATA);
};
</script>
</body>
