150 lines
4.7 KiB
JavaScript
150 lines
4.7 KiB
JavaScript
/**
|
||
* بکگراند ابرهای متحرک برای بخش Hero صفحه نخست
|
||
* شکل ابر: پایهٔ پهن + چند دایرهٔ روی هم (شکل ابر پفکی)
|
||
* @package Hesabix2
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
var container = document.getElementById('hero-clouds');
|
||
if (!container) return;
|
||
|
||
var canvas = document.createElement('canvas');
|
||
var ctx = canvas.getContext('2d');
|
||
if (!ctx) return;
|
||
|
||
var clouds = [];
|
||
var animationId = null;
|
||
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||
|
||
/**
|
||
* یک ابر با شکل طبیعی: یک بیضی پهن پایین + چند دایرهٔ روی هم برای قسمت پفکی
|
||
*/
|
||
function Cloud(options) {
|
||
this.x = options.x;
|
||
this.y = options.y;
|
||
this.size = options.size; // ضریب اندازه (۰.۲ تا ۰.۴)
|
||
this.speed = options.speed;
|
||
}
|
||
|
||
Cloud.prototype.draw = function (ctx, width, height) {
|
||
var cx = this.x * width;
|
||
var cy = this.y * height;
|
||
var baseW = width * this.size * 0.9;
|
||
var baseH = height * this.size * 0.22;
|
||
var r = Math.min(baseW, baseH) * 0.45;
|
||
|
||
ctx.save();
|
||
|
||
// سایهٔ ملایم زیر ابر (عمق و کنتراست)
|
||
ctx.shadowColor = 'rgba(0, 0, 0, 0.06)';
|
||
ctx.shadowBlur = 25;
|
||
ctx.shadowOffsetY = 8;
|
||
|
||
// رنگ ابر: سفید مات و کاملاً خوانا
|
||
ctx.fillStyle = 'rgba(255, 255, 255, 0.92)';
|
||
|
||
// پایهٔ ابر — بیضی پهن و کمارتفاع
|
||
ctx.beginPath();
|
||
ctx.ellipse(cx, cy + baseH * 0.5, baseW * 0.5, baseH * 0.7, 0, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// سهچهار دایرهٔ روی هم برای قسمت پفکی بالا (شکل ابر واقعی)
|
||
var bumps = [
|
||
{ x: -0.32, y: -0.35, r: 0.38 },
|
||
{ x: 0, y: -0.5, r: 0.42 },
|
||
{ x: 0.32, y: -0.35, r: 0.38 },
|
||
{ x: -0.12, y: -0.55, r: 0.28 },
|
||
{ x: 0.14, y: -0.52, r: 0.26 }
|
||
];
|
||
bumps.forEach(function (b) {
|
||
ctx.beginPath();
|
||
ctx.arc(
|
||
cx + b.x * baseW,
|
||
cy + b.y * baseH,
|
||
b.r * baseW * 0.55,
|
||
0, Math.PI * 2
|
||
);
|
||
ctx.fill();
|
||
});
|
||
|
||
ctx.restore();
|
||
};
|
||
|
||
function resize() {
|
||
var parent = container.parentElement;
|
||
if (!parent) return;
|
||
var w = parent.offsetWidth;
|
||
var h = parent.offsetHeight;
|
||
canvas.width = w * dpr;
|
||
canvas.height = h * dpr;
|
||
canvas.style.width = w + 'px';
|
||
canvas.style.height = h + 'px';
|
||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||
if (clouds.length === 0) initClouds(w, h);
|
||
}
|
||
|
||
function initClouds() {
|
||
clouds = [];
|
||
var count = 5;
|
||
for (var i = 0; i < count; i++) {
|
||
clouds.push(new Cloud({
|
||
x: Math.random() * 1.1 - 0.05,
|
||
y: 0.15 + Math.random() * 0.55,
|
||
size: 0.18 + Math.random() * 0.15,
|
||
speed: (0.00012 + Math.random() * 0.00008) * (i % 2 === 0 ? 1 : -1)
|
||
}));
|
||
}
|
||
}
|
||
|
||
function tick() {
|
||
var parent = container.parentElement;
|
||
if (!parent) return;
|
||
var w = parent.offsetWidth;
|
||
var h = parent.offsetHeight;
|
||
ctx.clearRect(0, 0, w, h);
|
||
clouds.forEach(function (c) {
|
||
c.x += c.speed;
|
||
if (c.speed > 0 && c.x > 1.35) c.x = -0.35;
|
||
if (c.speed < 0 && c.x < -0.35) c.x = 1.35;
|
||
c.draw(ctx, w, h);
|
||
});
|
||
animationId = requestAnimationFrame(tick);
|
||
}
|
||
|
||
function start() {
|
||
if (!container.parentElement) return;
|
||
container.appendChild(canvas);
|
||
resize();
|
||
tick();
|
||
}
|
||
|
||
var ro = typeof ResizeObserver !== 'undefined'
|
||
? new ResizeObserver(function () { resize(); })
|
||
: null;
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
start();
|
||
if (ro && container.parentElement) ro.observe(container.parentElement);
|
||
});
|
||
} else {
|
||
start();
|
||
if (ro && container.parentElement) ro.observe(container.parentElement);
|
||
}
|
||
|
||
window.addEventListener('resize', function () {
|
||
if (container.parentElement && canvas.parentNode) resize();
|
||
});
|
||
|
||
document.addEventListener('visibilitychange', function () {
|
||
if (document.hidden) {
|
||
if (animationId) {
|
||
cancelAnimationFrame(animationId);
|
||
animationId = null;
|
||
}
|
||
} else if (canvas.parentNode) {
|
||
tick();
|
||
}
|
||
});
|
||
})();
|