<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>植物生长模拟</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background-color: #f0f5ff;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
#container {
position: relative;
width: 1080px;
height: 1920px;
margin: 0 auto;
/* 渐变背景:草地 → 天空 */
background: linear-gradient(to top,
#8bc9a0 20%, /* 深绿 - 土壤层 */
#a0d8a0 30%, /* 浅绿 - 地面过渡 */
#b0e0b0 35%,
#8bc9e0 45%,
#7ab9ff 60%,
#6aa3ff 100%); /* 蓝天 */
transform-origin: 0 0;
overflow: hidden;
}
/* 标题 */
.header {
position: absolute;
top: 40px;
left: 0;
width: 100%;
text-align: center;
z-index: 10;
color: white;
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}
.header h1 {
font-size: 64px;
font-weight: bold;
letter-spacing: 4px;
}
/* 太阳 */
#sun {
position: absolute;
top: 200px;
left: 50%;
transform: translateX(-50%);
width: 110px;
height: 110px;
background-color: #FFD700;
border-radius: 50%;
box-shadow: 0 0 60px #FFA500;
z-index: 11;
transition: left 0.6s cubic-bezier(0.4, 0, 0.2, 1);
border: 6px solid rgba(255, 255, 255, 0.6);
}
#sun::after {
content: '';
position: absolute;
top: -30px;
left: -30px;
right: -30px;
bottom: -30px;
border-radius: 50%;
box-shadow: 0 0 90px 25px rgba(255, 200, 0, 0.5);
z-index: -1;
}
/* 控制面板 */
.control-panel {
position: absolute;
top: 260px;
left: 60px;
z-index: 10;
color: white;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
background-color: rgba(255, 255, 255, 0.15);
padding: 30px;
border-radius: 20px;
min-width: 420px;
}
.slider-group {
margin-bottom: 40px;
}
label {
font-size: 36px;
}
input[type="range"] {
width: 100%;
height: 16px;
margin-top: 12px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.4);
border-radius: 8px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 32px;
height: 32px;
background: #ffffff;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
/* 画布:植物绘制区域 */
canvas {
position: absolute;
bottom: 220px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
}
/* 按钮组 */
.button-group {
position: absolute;
bottom: 180px;
left: 0;
width: 100%;
display: flex;
justify-content: center;
gap: 40px;
padding: 0 60px;
box-sizing: border-box;
}
button {
flex: 1;
max-width: 400px;
padding: 24px 0;
font-size: 38px;
font-weight: bold;
border: none;
border-radius: 20px;
cursor: pointer;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
color: white;
transition: transform 0.2s ease, opacity 0.2s ease;
}
button:hover {
transform: translateY(-4px);
}
button:active {
transform: translateY(2px);
}
#growBtn {
background: linear-gradient(to top, #4CAF50, #66bb6a);
}
#homeBtn {
background: linear-gradient(to top, #2196F3, #42a5f5);
}
/* 隐藏的跳转链接 */
#homeLink {
display: none;
}
</style>
</head>
<body>
<div id="container">
<!-- 标题 -->
<div class="header">
<h1>植物生长模拟</h1>
</div>
<!-- 太阳 -->
<div id="sun"></div>
<!-- 画布 -->
<canvas id="plantCanvas"></canvas>
<!-- 控制面板 -->
<div class="control-panel">
<div class="slider-group">
<label>光照方向: <span id="lightValue">0°</span></label><br>
<input type="range" id="lightSlider" min="-60" max="60" value="0" step="1" />
</div>
<div class="slider-group">
<label>湿度: <span id="moistureValue">50%</span></label><br>
<input type="range" id="moistureSlider" min="0" max="1" value="0.5" step="0.01" />
</div>
</div>
<!-- 按钮组 -->
<div class="button-group">
<button id="growBtn">开始生长</button>
<button id="homeBtn">返回首页</button>
</div>
<!-- 隐藏的跳转链接(用于可靠打开本地文件) -->
<a id="homeLink" href="file:///Users/wanghanyi/Desktop/自然算法/Algorithm%20Visualizer-2.html">返回首页</a>
</div>
<script>
// 设计分辨率
const TARGET_WIDTH = 1080;
const TARGET_HEIGHT = 1920;
// 获取 canvas 和上下文
const canvas = document.getElementById('plantCanvas');
const ctx = canvas.getContext('2d');
canvas.width = TARGET_WIDTH;
canvas.height = TARGET_HEIGHT;
// 响应式缩放
function resize() {
const scale = Math.min(
window.innerWidth / TARGET_WIDTH,
window.innerHeight / TARGET_HEIGHT
);
document.getElementById('container').style.transform = `scale(${scale})`;
}
window.addEventListener('resize', resize);
resize();
// UI 元素
const lightSlider = document.getElementById('lightSlider');
const moistureSlider = document.getElementById('moistureSlider');
const lightValue = document.getElementById('lightValue');
const moistureValue = document.getElementById('moistureValue');
const sun = document.getElementById('sun');
const growBtn = document.getElementById('growBtn');
const homeBtn = document.getElementById('homeBtn');
// 环境参数
let env = {
lightDirection: 0,
moisture: 0.5,
};
// 更新 UI 并同步太阳位置
function updateUI() {
env.lightDirection = parseFloat(lightSlider.value);
env.moisture = parseFloat(moistureSlider.value);
lightValue.textContent = `${env.lightDirection}°`;
moistureValue.textContent = `${(env.moisture * 100).toFixed(0)}%`;
const centerX = TARGET_WIDTH / 2;
const deltaX = env.lightDirection * 10;
const sunX = Math.max(100, Math.min(TARGET_WIDTH - 100, centerX + deltaX));
sun.style.left = `${sunX}px`;
}
lightSlider.addEventListener('input', updateUI);
moistureSlider.addEventListener('input', updateUI);
updateUI(); // 初始化
// ======================
// 植物类:向光性增强版本
// ======================
class EnhancedPhototropicPlant {
constructor(axiom, rules, baseAngle, stepLength, iterations, env) {
this.axiom = axiom;
this.rules = rules;
this.baseAngle = baseAngle;
this.stepLength = stepLength;
this.iterations = iterations;
this.env = env;
this.sentence = this.generate();
this.drawIndex = 0;
}
generate() {
let result = this.axiom;
for (let i = 0; i < this.iterations; i++) {
let next = '';
for (let char of result) {
next += this.rules[char] || char;
// 高湿环境下增加分枝概率
if (char === 'F' && this.env.moisture > 0.7 && Math.random() < 0.1) {
next = next.replace('F', 'F[+F]F[-F]F');
}
}
result = next;
}
return result;
}
start(ctx) {
this.drawIndex = 0;
this._animateFrame(ctx);
}
_animateFrame(ctx) {
if (this.drawIndex === 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
ctx.save();
// ✅ 与 CSS 中的 bottom: 220px 对齐
const GROUND_Y = TARGET_HEIGHT - 220;
ctx.translate(TARGET_WIDTH / 2, GROUND_Y);
const stack = [];
const { stepLength, baseAngle, env } = this;
const lightDir = env.lightDirection;
let cumulativeBend = 0;
const maxBendPerStep = 0.8;
const bendFactor = Math.abs(lightDir) / 60 * 1.2; // 弯曲强度随光照偏移增大
for (let i = 0; i < this.drawIndex; i++) {
const char = this.sentence[i];
switch (char) {
case 'F':
// 累积弯曲角度
cumulativeBend += bendFactor * maxBendPerStep;
ctx.strokeStyle = '#2E8B57';
ctx.lineWidth = Math.max(3, stepLength / 5);
ctx.beginPath();
ctx.moveTo(0, 0);
const currentBend = cumulativeBend * Math.sign(lightDir);
ctx.rotate((Math.PI / 180) * currentBend);
ctx.lineTo(0, -stepLength);
ctx.stroke();
ctx.translate(0, -stepLength);
break;
case '+':
ctx.rotate((Math.PI / 180) * (baseAngle + Math.abs(lightDir) * 0.5));
break;
case '-':
ctx.rotate(-(Math.PI / 180) * (baseAngle + Math.abs(lightDir) * 0.5));
break;
case '[':
stack.push({
x: ctx.getTransform().e,
y: ctx.getTransform().f,
matrix: [ctx.getTransform().a, ctx.getTransform().b],
bend: cumulativeBend
});
break;
case ']':
if (stack.length > 0) {
const state = stack.pop();
ctx.setTransform(1, 0, 0, 1, state.x, state.y);
const rotation = Math.atan2(state.matrix[1], state.matrix[0]);
ctx.rotate(rotation);
cumulativeBend = state.bend;
}
break;
}
}
ctx.restore();
this.drawIndex++;
if (this.drawIndex <= this.sentence.length) {
requestAnimationFrame(() => this._animateFrame(ctx));
}
}
}
// 开始生长按钮
growBtn.addEventListener('click', () => {
const plant = new EnhancedPhototropicPlant(
'F',
{ 'F': 'F[+F][-F]F' },
25,
36,
4,
env
);
plant.start(ctx);
});
// 返回首页按钮:使用隐藏 <a> 标签实现 file:// 可靠跳转
homeBtn.addEventListener('click', () => {
const link = document.getElementById('homeLink');
console.log('尝试跳转到:', link.href);
try {
link.click(); // 利用原生链接行为提高成功率
} catch (e) {
window.location.href = link.href; // 回退方案
}
});
</script>
</body>
</html>
我希望植物的生长方向由太阳来决定,在植物的生长过程中,太阳如果发生了移动角度,则顶端新生的枝桠应该跟随太阳的位置,而不是现在整个树在生长好以后跟着太阳走