
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分离定律 · 多轮模拟记录</title>
<style>
* {
box-sizing: border-box;
font-family: system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
}
body {
background: linear-gradient(145deg, #f0f7e8 0%, #e1efe0 100%);
min-height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
padding: 16px;
}
.card {
max-width: 1100px;
width: 100%;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(6px);
background-color: #fefefee0;
border-radius: 42px;
box-shadow: 0 25px 40px -12px rgba(40, 70, 40, 0.4), inset 0 1px 3px #ffffffcc;
padding: 30px 30px 40px;
border: 1px solid #d0e0c0;
}
h1 {
margin: 0 0 8px 0;
font-weight: 600;
font-size: 2.2rem;
color: #1f4f2b;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
h1 small {
font-size: 1rem;
font-weight: 400;
background: #d4e6cf;
color: #1f4f2b;
padding: 6px 15px;
border-radius: 40px;
}
.subhead {
color: #2d5a3a;
border-left: 6px solid #76b583;
padding-left: 20px;
margin: 8px 0 25px 0;
font-size: 1.1rem;
font-weight: 350;
background: #ecf6e9;
border-radius: 0 40px 40px 0;
line-height: 1.6;
width: fit-content;
padding: 12px 25px;
}
.law-badge {
background: #2d5a3a;
color: #f9fff5;
padding: 4px 18px;
border-radius: 28px;
font-weight: 500;
font-size: 0.85rem;
display: inline-block;
margin-bottom: 20px;
letter-spacing: 0.5px;
}
.parents-panel {
background: #e5f0df;
border-radius: 36px;
padding: 22px 28px;
margin-bottom: 30px;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
box-shadow: inset 0 2px 5px #b5c9ac, 0 10px 15px -8px #8faa84;
}
.parent {
text-align: center;
min-width: 150px;
}
.parent .genotype {
font-size: 2.4rem;
font-weight: 700;
background: #ffffffc9;
display: inline-block;
padding: 10px 30px;
border-radius: 60px;
box-shadow: 0 6px 0 #8ba381;
color: #17331e;
letter-spacing: 4px;
border: 2px solid #afc8a5;
margin: 10px 0 5px;
}
.parent .label {
font-weight: 600;
color: #1f4f2b;
background: #cbe3c2;
padding: 6px 18px;
border-radius: 30px;
font-size: 1rem;
display: inline-block;
}
/* 减数分裂可视化区域 */
.meiosis-process {
background: #e0efd8;
border-radius: 50px;
padding: 25px 25px 20px;
margin: 15px 0 30px;
box-shadow: inset 0 1px 8px #b0cdae, 0 10px 12px -8px #6e8f66;
}
.meiosis-title {
font-weight: 600;
color: #1f4f2b;
font-size: 1.25rem;
margin: 0 0 20px 10px;
display: flex;
align-items: center;
gap: 10px;
}
.meiosis-title span {
background: #3b6e49;
color: white;
padding: 5px 15px;
border-radius: 30px;
font-size: 0.9rem;
}
.split-panel {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 25px;
}
.parent-meiosis {
flex: 1 1 250px;
background: #f1faecc0;
border-radius: 40px;
padding: 15px 15px 20px;
border: 3px solid #c1ddb5;
box-shadow: 0 4px 0 #8bb17e;
}
.parent-meiosis .parent-title {
font-size: 1.3rem;
font-weight: 600;
color: #1f4f2b;
text-align: center;
background: #d0e7c4;
border-radius: 30px;
padding: 8px;
margin-top: -5px;
}
.somatic-cell {
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
margin: 18px 0 5px;
}
.chromosome-pair {
background: #fff5d9;
border-radius: 40px;
padding: 12px 25px;
border: 3px solid #b5945a;
display: inline-flex;
gap: 20px;
box-shadow: 0 4px 0 #9e7f48;
}
.chromosome {
font-size: 2.2rem;
font-weight: 700;
background: #ffffff;
width: 70px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 3px solid #6d8c60;
color: #1b4b1b;
box-shadow: 0 3px 0 #4a6940;
}
.arrow-down {
font-size: 2rem;
color: #527a4c;
margin: 5px 0;
text-align: center;
}
.gametes-result {
display: flex;
justify-content: center;
gap: 35px;
margin: 10px 0 5px;
}
.gamete-item {
text-align: center;
}
.gamete-circle {
width: 65px;
height: 65px;
background: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: 700;
border: 3px solid #3f6645;
box-shadow: 0 5px 0 #2b4d2b;
color: #1d4f1d;
margin-bottom: 6px;
}
.gamete-label {
background: #2d5a3a;
color: white;
padding: 4px 12px;
border-radius: 30px;
font-size: 0.9rem;
font-weight: 500;
}
.process-note {
text-align: center;
color: #2d5a3a;
font-weight: 500;
margin-top: 20px;
background: #c6e0bb;
padding: 10px;
border-radius: 40px;
font-size: 1rem;
}
.process-note i {
font-style: normal;
background: #f9fff5;
padding: 3px 10px;
border-radius: 30px;
margin: 0 4px;
}
.control-area {
background: #ffffffd6;
border-radius: 60px;
padding: 18px 30px;
margin: 25px 0 30px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 25px;
border: 2px dashed #98b38b;
}
.input-group {
display: flex;
align-items: center;
gap: 12px;
background: #ecf6e9;
padding: 8px 20px;
border-radius: 40px;
}
.input-group label {
font-weight: 600;
color: #1f4f2b;
}
.input-group input {
width: 100px;
padding: 10px 15px;
border: 2px solid #b1cfaa;
border-radius: 40px;
font-size: 1.1rem;
font-weight: 600;
text-align: center;
background: white;
outline: none;
transition: 0.2s;
}
.input-group input:focus {
border-color: #2f7d4a;
box-shadow: 0 0 0 3px #b3dfb3;
}
.simulate-btn {
background: #2b7342;
border: none;
color: white;
font-size: 1.5rem;
font-weight: 600;
padding: 16px 45px;
border-radius: 60px;
cursor: pointer;
box-shadow: 0 10px 0 #124d24, 0 6px 18px #1d512d80;
transition: 0.07s linear;
border: 2px solid #9fcc9a;
letter-spacing: 1px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.simulate-btn:active {
transform: translateY(7px);
box-shadow: 0 3px 0 #124d24, 0 6px 18px #1d512d80;
}
.reset-btn {
background: #b0b0b0;
border: none;
color: white;
font-size: 1.3rem;
font-weight: 600;
padding: 12px 30px;
border-radius: 60px;
cursor: pointer;
box-shadow: 0 6px 0 #6e6e6e, 0 6px 18px #5d5d5d80;
transition: 0.07s linear;
border: 2px solid #d0d0d0;
display: inline-flex;
align-items: center;
gap: 6px;
}
.reset-btn:active {
transform: translateY(4px);
box-shadow: 0 2px 0 #6e6e6e, 0 6px 18px #5d5d5d80;
}
.results {
background: #f6fff0;
border-radius: 40px;
padding: 20px 20px 30px;
border: 3px solid #bfdbaf;
}
.results h3 {
color: #1d4f2a;
margin: 0 0 8px 20px;
font-size: 1.6rem;
font-weight: 500;
}
.barchart-container {
display: flex;
justify-content: space-around;
align-items: flex-end;
gap: 30px;
padding: 20px 10px;
min-height: 250px;
}
.bar-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 150px;
}
.bar-label {
font-weight: 700;
font-size: 1.4rem;
margin-top: 10px;
background: #ddecd4;
width: 100%;
padding: 8px 0;
border-radius: 40px;
color: #17331e;
}
.bar-outer {
height: 200px;
width: 100%;
display: flex;
flex-direction: column-reverse;
border-radius: 30px 30px 15px 15px;
background: #e3eedb;
border: 3px solid #a0bb95;
overflow: hidden;
margin-bottom: 8px;
box-shadow: inset 0 2px 6px #8f8f8f33;
}
.bar-inner {
width: 100%;
transition: height 0.3s ease;
border-radius: 12px 12px 0 0;
}
.bar-inner.AA { background: #5fa7d1; }
.bar-inner.Aa { background: #6fbf73; }
.bar-inner.aa { background: #e8a35e; }
.count-info {
font-size: 1.1rem;
font-weight: 600;
color: #1e3d24;
margin-top: 5px;
}
.percent-info {
font-size: 1rem;
background: #d0e0c0;
padding: 3px 15px;
border-radius: 50px;
color: #1b4f1b;
font-weight: 600;
margin-top: 5px;
}
.phenotype-row {
display: flex;
justify-content: space-around;
margin: 25px 10px 10px;
background: #deedd4;
border-radius: 100px;
padding: 18px 20px;
}
.phenotype-block {
display: flex;
align-items: center;
gap: 18px;
flex-wrap: wrap;
justify-content: center;
}
.phenotype-dot {
width: 32px;
height: 32px;
border-radius: 50%;
background: #d9b650;
}
.phenotype-dot.dominant { background: #5180b0; }
.phenotype-dot.recessive { background: #c07a4a; }
.phenotype-value {
font-size: 1.5rem;
font-weight: 700;
color: #1e3f1e;
}
/* 新增:历史记录表格(多轮模拟总体结果) */
.history-area {
margin-top: 30px;
background: #eaf3e3;
border-radius: 40px;
padding: 18px 20px;
border: 3px solid #b1cfaa;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 15px;
}
.history-header h4 {
margin: 0;
font-size: 1.4rem;
color: #1f4f2b;
font-weight: 600;
}
.history-table-wrapper {
max-height: 250px;
overflow-y: auto;
border-radius: 30px;
background: #ffffffc9;
padding: 5px;
box-shadow: inset 0 2px 6px #bacdb0;
}
table {
width: 100%;
border-collapse: collapse;
text-align: center;
font-size: 0.95rem;
}
th {
background: #2d5a3a;
color: white;
padding: 12px 5px;
font-weight: 500;
position: sticky;
top: 0;
}
td {
padding: 10px 5px;
border-bottom: 1px solid #c8ddbc;
}
.history-note {
margin-top: 10px;
font-size: 0.95rem;
color: #3b6e49;
text-align: center;
}
.footnote {
margin-top: 20px;
text-align: center;
color: #406d48;
font-style: italic;
font-weight: 500;
background: #dcedd1;
padding: 12px;
border-radius: 60px;
}
hr {
border: 2px solid #c0dbc0;
border-radius: 5px;
margin: 25px 0 15px;
}
.button-group {
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
}
</style>
</head>
<body>
<div class="card">
<h1>
🌱 分离定律 · 多轮模拟记录
<small>Aa × Aa</small>
</h1>
<div class="subhead">
⚡ 体细胞中成对基因(Aa)在减数分裂时分离 → 配子只含一个等位基因(A或a) → 受精随机结合
</div>
<!-- 亲本展示(体细胞) -->
<div class="parents-panel">
<div class="parent">
<span class="label">父本体细胞 ♂</span>
<div class="genotype">Aa</div>
</div>
<div style="font-size: 3rem; color: #3b6e49;">⨯</div>
<div class="parent">
<span class="label">母本体细胞 ♀</span>
<div class="genotype">Aa</div>
</div>
</div>
<!-- 减数分裂过程(体细胞→配子) -->
<div class="meiosis-process">
<div class="meiosis-title">
🧬 减数分裂 · 等位基因分离
<span>同源染色体分开</span>
</div>
<div class="split-panel">
<!-- 父本减数分裂 -->
<div class="parent-meiosis">
<div class="parent-title">父本 ♂ 减数分裂</div>
<div class="somatic-cell">
<div class="chromosome-pair">
<div class="chromosome">A</div>
<div class="chromosome">a</div>
</div>
</div>
<div class="arrow-down">⬇ 分离 ⬇</div>
<div class="gametes-result">
<div class="gamete-item">
<div class="gamete-circle">A</div>
<div class="gamete-label">配子 50%</div>
</div>
<div class="gamete-item">
<div class="gamete-circle">a</div>
<div class="gamete-label">配子 50%</div>
</div>
</div>
</div>
<!-- 母本减数分裂 -->
<div class="parent-meiosis">
<div class="parent-title">母本 ♀ 减数分裂</div>
<div class="somatic-cell">
<div class="chromosome-pair">
<div class="chromosome">A</div>
<div class="chromosome">a</div>
</div>
</div>
<div class="arrow-down">⬇ 分离 ⬇</div>
<div class="gametes-result">
<div class="gamete-item">
<div class="gamete-circle">A</div>
<div class="gamete-label">配子 50%</div>
</div>
<div class="gamete-item">
<div class="gamete-circle">a</div>
<div class="gamete-label">配子 50%</div>
</div>
</div>
</div>
</div>
<div class="process-note">
<i>等位基因随同源染色体分离而分开</i> → 每个配子获得一个等位基因(A 或 a),概率相等
</div>
</div>
<!-- 控制区域:模拟受精 + 重置 -->
<div class="control-area">
<div class="input-group">
<label for="offspringCount">🌱 每轮子代数量</label>
<input type="number" id="offspringCount" min="10" max="10000" value="800" step="10">
</div>
<div class="button-group">
<button class="simulate-btn" id="simulateBtn">
<span>▶</span> 开始一轮模拟
</button>
<button class="reset-btn" id="resetBtn">
<span>⟲</span> 重置所有记录
</button>
</div>
</div>
<!-- 当前结果图表 -->
<div class="results">
<h3>📊 本轮子代基因型分布</h3>
<div class="barchart-container" id="barContainer">
<div class="bar-item">
<div class="bar-outer" id="barAAOuter">
<div class="bar-inner AA" id="barAA" style="height: 0%;"></div>
</div>
<div class="bar-label">AA</div>
<div class="count-info" id="countAA">0</div>
<div class="percent-info" id="percAA">0.0%</div>
</div>
<div class="bar-item">
<div class="bar-outer" id="barAaOuter">
<div class="bar-inner Aa" id="barAa" style="height: 0%;"></div>
</div>
<div class="bar-label">Aa</div>
<div class="count-info" id="countAa">0</div>
<div class="percent-info" id="percAa">0.0%</div>
</div>
<div class="bar-item">
<div class="bar-outer" id="baRaaOuter">
<div class="bar-inner aa" id="barAa_aa" style="height: 0%;"></div>
</div>
<div class="bar-label">aa</div>
<div class="count-info" id="countaa">0</div>
<div class="percent-info" id="percaa">0.0%</div>
</div>
</div>
<!-- 表现型总结 -->
<div class="phenotype-row">
<div class="phenotype-block">
<span class="phenotype-dot dominant"></span>
<span style="font-weight:600;">高茎 (显性 A_ ) :</span>
<span class="phenotype-value" id="dominantCount">0</span>
<span style="font-size:1.2rem;font-weight:500;" id="dominantPerc">(0.0%)</span>
</div>
<div class="phenotype-block">
<span class="phenotype-dot recessive"></span>
<span style="font-weight:600;">矮茎 (隐性 aa) :</span>
<span class="phenotype-value" id="recessiveCount">0</span>
<span style="font-size:1.2rem;font-weight:500;" id="recessivePerc">(0.0%)</span>
</div>
</div>
</div>
<!-- 新增:多轮模拟历史记录 -->
<div class="history-area">
<div class="history-header">
<h4>📋 历次模拟总体结果</h4>
<span style="font-size:0.9rem; color:#2d5a3a;">每次点击“开始一轮模拟”都会新增一行记录</span>
</div>
<div class="history-table-wrapper">
<table id="historyTable">
<thead>
<tr>
<th>轮次</th>
<th>样本量(N)</th>
<th>AA</th>
<th>Aa</th>
<th>aa</th>
<th>AA%</th>
<th>Aa%</th>
<th>aa%</th>
<th>显性%</th>
</tr>
</thead>
<tbody id="historyBody">
<!-- 动态生成 -->
</tbody>
</table>
</div>
<div class="history-note" id="historyNote">暂无历史记录,点击按钮开始模拟</div>
</div>
<hr>
<div style="text-align: right; color: #3f6b48; padding-right: 10px;">
<span class="law-badge">分离定律 · 孟德尔第一定律</span>
</div>
</div>
<script>
(function() {
// DOM 元素
const simulateBtn = document.getElementById('simulateBtn');
const resetBtn = document.getElementById('resetBtn');
const countInput = document.getElementById('offspringCount');
// 当前统计显示
const countAA = document.getElementById('countAA');
const countAa = document.getElementById('countAa');
const countaa = document.getElementById('countaa');
const percAA = document.getElementById('percAA');
const percAa = document.getElementById('percAa');
const percaa = document.getElementById('percaa');
// 柱状图
const barAA = document.getElementById('barAA');
const barAa = document.getElementById('barAa');
const baraa = document.getElementById('barAa_aa');
// 表现型
const dominantCountSpan = document.getElementById('dominantCount');
const recessiveCountSpan = document.getElementById('recessiveCount');
const dominantPercSpan = document.getElementById('dominantPerc');
const recessivePercSpan = document.getElementById('recessivePerc');
// 历史记录表格
const historyBody = document.getElementById('historyBody');
const historyNote = document.getElementById('historyNote');
// 存储历史数据,每个元素包含轮次、总数、AA、Aa、aa
let historyData = [];
let roundCounter = 0; // 记录模拟轮次
// 更新当前统计和柱状图
function updateCurrentResults(counts) {
const { AA, Aa, aa } = counts;
const total = AA + Aa + aa;
if (total === 0) {
barAA.style.height = '0%';
barAa.style.height = '0%';
baraa.style.height = '0%';
countAA.innerText = '0';
countAa.innerText = '0';
countaa.innerText = '0';
percAA.innerText = '0.0%';
percAa.innerText = '0.0%';
percaa.innerText = '0.0%';
dominantCountSpan.innerText = '0';
recessiveCountSpan.innerText = '0';
dominantPercSpan.innerText = '(0.0%)';
recessivePercSpan.innerText = '(0.0%)';
return;
}
const percAAVal = (AA / total * 100).toFixed(1);
const percAaVal = (Aa / total * 100).toFixed(1);
const percaaVal = (aa / total * 100).toFixed(1);
barAA.style.height = percAAVal + '%';
barAa.style.height = percAaVal + '%';
baraa.style.height = percaaVal + '%';
countAA.innerText = AA;
countAa.innerText = Aa;
countaa.innerText = aa;
percAA.innerText = percAAVal + '%';
percAa.innerText = percAaVal + '%';
percaa.innerText = percaaVal + '%';
const dominant = AA + Aa;
const recessive = aa;
const percDom = (dominant / total * 100).toFixed(1);
const percRec = (recessive / total * 100).toFixed(1);
dominantCountSpan.innerText = dominant;
recessiveCountSpan.innerText = recessive;
dominantPercSpan.innerText = `(${percDom}%)`;
recessivePercSpan.innerText = `(${percRec}%)`;
}
// 更新历史记录表格
function updateHistoryTable() {
historyBody.innerHTML = '';
if (historyData.length === 0) {
historyNote.style.display = 'block';
return;
}
historyNote.style.display = 'none';
// 按轮次正序显示(最新的在后面)
historyData.forEach(rec => {
const row = document.createElement('tr');
// 轮次
const tdRound = document.createElement('td'); tdRound.textContent = rec.round; row.appendChild(tdRound);
// 样本量
const tdN = document.createElement('td'); tdN.textContent = rec.total; row.appendChild(tdN);
// AA, Aa, aa 数量
const tdAA = document.createElement('td'); tdAA.textContent = rec.AA; row.appendChild(tdAA);
const tdAa = document.createElement('td'); tdAa.textContent = rec.Aa; row.appendChild(tdAa);
const tdaa = document.createElement('td'); tdaa.textContent = rec.aa; row.appendChild(tdaa);
// 百分比 (保留1位小数)
const percAA = (rec.AA / rec.total * 100).toFixed(1);
const percAa = (rec.Aa / rec.total * 100).toFixed(1);
const percaa = (rec.aa / rec.total * 100).toFixed(1);
const percDom = ((rec.AA + rec.Aa) / rec.total * 100).toFixed(1);
const tdPercAA = document.createElement('td'); tdPercAA.textContent = percAA + '%'; row.appendChild(tdPercAA);
const tdPercAa = document.createElement('td'); tdPercAa.textContent = percAa + '%'; row.appendChild(tdPercAa);
const tdPercaa = document.createElement('td'); tdPercaa.textContent = percaa + '%'; row.appendChild(tdPercaa);
const tdPercDom = document.createElement('td'); tdPercDom.textContent = percDom + '%'; row.appendChild(tdPercDom);
historyBody.appendChild(row);
});
}
// 模拟一轮,返回 counts 对象
function simulateRound(offspringCount) {
let AA = 0, Aa = 0, aa = 0;
for (let i = 0; i < offspringCount; i++) {
const paternal = Math.random() < 0.5 ? 'A' : 'a';
const maternal = Math.random() < 0.5 ? 'A' : 'a';
if (paternal === 'A' && maternal === 'A') AA++;
else if (paternal === 'a' && maternal === 'a') aa++;
else Aa++;
}
return { AA, Aa, aa };
}
// 处理模拟按钮点击
function onSimulate() {
let number = parseInt(countInput.value, 10);
if (isNaN(number) || number < 1) {
number = 100;
countInput.value = 100;
} else if (number > 20000) {
number = 20000;
countInput.value = 20000;
}
const counts = simulateRound(number);
// 更新当前图表
updateCurrentResults(counts);
// 添加到历史记录
roundCounter++;
const total = counts.AA + counts.Aa + counts.aa;
historyData.push({
round: roundCounter,
total: total,
AA: counts.AA,
Aa: counts.Aa,
aa: counts.aa
});
updateHistoryTable();
}
// 重置所有
function resetAll() {
// 清空当前图表
updateCurrentResults({ AA: 0, Aa: 0, aa: 0 });
// 清空历史数据
historyData = [];
roundCounter = 0;
updateHistoryTable();
// 输入框保留原值,不清空
}
// 绑定事件
simulateBtn.addEventListener('click', onSimulate);
resetBtn.addEventListener('click', resetAll);
// 输入框回车触发模拟
countInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
onSimulate();
}
});
// 输入框失焦时校正
countInput.addEventListener('blur', () => {
let val = parseInt(countInput.value, 10);
if (isNaN(val) || val < 1) {
countInput.value = 100;
} else if (val > 20000) {
countInput.value = 20000;
}
});
// 初始状态为空(不自动模拟)
resetAll();
})();
</script>
</body>
</html>
夜雨聆风