← Модули · Тильда · Zero Block
Неоновые кольца прогресса для Zero Block
БесплатноZero Block с классом uc-neon-block. У круглого Shape — бордер в цвет неона и data-bg / data-rd / data-pc / data-speed. Текст с классом animate — scramble на время анимации кольца.
Файлы
tilda-neon-rings.js — SVG-дуга + glow только на progress
Инструкция
- На Zero Block класс: uc-neon-block.
- Круглый Shape с бордером в цвет неона (толщина бордера = толщина кольца).
- DATA ATTRIBUTE на Shape:
- • data-bg — % прозрачности цвета бордера для трека.
- • data-rd — радиус свечения заполненной дуги (px).
- • data-pc — % заполнения круга.
- • data-speed — длительность анимации в секундах.
- Текст: класс animate.
- T123 — код целиком (с <script>). Несколько колец: каждое стартует, когда попадает в экран.
Код
<script>
/* ============================================
YM Neon Rings для Тильды Zero Block
Блок: uc-neon-block
Shape: data-bg / data-rd / data-pc / data-speed
Текст .animate — scramble на время data-speed
https://ymportfolio.ru/modules/tilda-neon-rings/
============================================ */
(function () {
var CFG = {
block: "uc-neon-block",
animate: "animate",
bg: 10,
rd: 30,
pc: 0,
speed: 1.15,
scrambleChars:
"ABCDEFGHJKLMNPQRSTUVWXYZАБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЫЭЮЯ0123456789#%*+<>"
};
var css = [
".ym-neon-host{position:relative!important;}",
".ym-neon-host.ym-neon-ready > .tn-atom{",
" border-color:transparent!important;box-shadow:none!important;",
" -webkit-box-shadow:none!important;}",
".ym-neon-svg{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) rotate(-90deg);",
" width:100%;height:100%;overflow:visible;pointer-events:none;z-index:2;}",
".ym-neon-svg circle{fill:none;stroke-linecap:round;}",
".ym-neon-svg .ym-neon-track{transition:none;}",
".ym-neon-svg .ym-neon-glow,.ym-neon-svg .ym-neon-bar{",
" stroke-dashoffset:var(--ym-neon-len);",
" transition:stroke-dashoffset var(--ym-neon-dur,1.15s) cubic-bezier(.2,.8,.2,1);}",
".ym-neon-host.is-on .ym-neon-glow,.ym-neon-host.is-on .ym-neon-bar{",
" stroke-dashoffset:var(--ym-neon-off);}"
].join("");
function injectCss() {
if (document.getElementById("ym-neon-rings-css")) return;
var s = document.createElement("style");
s.id = "ym-neon-rings-css";
s.textContent = css;
document.head.appendChild(s);
}
function num(v, fallback) {
var n = parseFloat(String(v == null ? "" : v).replace(",", "."));
return isNaN(n) ? fallback : n;
}
function attr(el, name, fallback) {
var atom = el.querySelector && el.querySelector(".tn-atom");
var raw = el.getAttribute(name) || (atom && atom.getAttribute(name)) || "";
return num(raw, fallback);
}
function parseRgb(color) {
if (!color) return { r: 255, g: 255, b: 255 };
color = String(color).trim();
if (color.charAt(0) === "#") {
var h = color.slice(1);
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
var n = parseInt(h, 16);
if (isNaN(n)) return { r: 255, g: 255, b: 255 };
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
var m = color.match(/rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
if (m) return { r: +m[1], g: +m[2], b: +m[3] };
return { r: 255, g: 255, b: 255 };
}
function rgba(c, a) {
return "rgba(" + c.r + "," + c.g + "," + c.b + "," + a + ")";
}
function findBlocks() {
var name = CFG.block.replace(/^\./, "");
return Array.prototype.slice.call(
document.querySelectorAll("." + name + ", .uc-" + name)
);
}
function findRings(block) {
var out = [];
var seen = [];
Array.prototype.forEach.call(block.querySelectorAll("[data-pc]"), function (node) {
var el =
node.classList && node.classList.contains("tn-elem")
? node
: node.closest(".tn-elem") || node;
if (!el || seen.indexOf(el) !== -1) return;
seen.push(el);
out.push(el);
});
return out;
}
function findAnimate(block) {
var name = (CFG.animate || "animate").replace(/^\./, "");
var nodes = block.querySelectorAll("." + name + ", .uc-" + name);
var out = [];
var seen = [];
Array.prototype.forEach.call(nodes, function (node) {
var el =
node.classList && node.classList.contains("tn-elem")
? node
: node.closest(".tn-elem") || node;
if (!el || seen.indexOf(el) !== -1) return;
seen.push(el);
out.push(el);
});
return out;
}
function textRoot(el) {
return (el && el.querySelector && el.querySelector(".tn-atom")) || el;
}
function collectTextNodes(root) {
var list = [];
function walk(n) {
if (!n) return;
if (n.nodeType === 3) {
list.push(n);
return;
}
if (n.nodeType !== 1) return;
var kids = n.childNodes;
for (var i = 0; i < kids.length; i++) walk(kids[i]);
}
walk(root);
return list;
}
function randChar() {
var set = CFG.scrambleChars || "ABCabc123#*";
return set.charAt((Math.random() * set.length) | 0);
}
function ringSpeed(el) {
var s = attr(el, "data-speed", CFG.speed);
if (s <= 0) s = CFG.speed;
if (s > 20) s = s / 1000;
return s;
}
function scrambleText(el, durationSec) {
var root = textRoot(el);
if (!root || root.getAttribute("data-ym-scramble") === "1") return;
root.setAttribute("data-ym-scramble", "1");
var nodes = collectTextNodes(root).filter(function (n) {
return n.nodeValue && n.nodeValue.length;
});
if (!nodes.length) return;
var originals = nodes.map(function (n) {
return n.nodeValue;
});
var dur = Math.max(0.2, durationSec || CFG.speed || 1.15) * 1000;
var t0 = null;
function paint(p) {
nodes.forEach(function (node, idx) {
var orig = originals[idx];
var out = "";
var len = orig.length || 1;
for (var i = 0; i < orig.length; i++) {
var ch = orig.charAt(i);
if (/\s/.test(ch)) {
out += ch;
continue;
}
var settle = (i + 0.65) / len;
if (p >= settle) out += ch;
else if (p > settle - 0.18) out += Math.random() > 0.35 ? ch : randChar();
else out += randChar();
}
node.nodeValue = out;
});
}
paint(0);
function frame(now) {
if (t0 == null) t0 = now;
var p = Math.min(1, (now - t0) / dur);
paint(1 - Math.pow(1 - p, 2.2));
if (p < 1) requestAnimationFrame(frame);
else {
nodes.forEach(function (node, idx) {
node.nodeValue = originals[idx];
});
}
}
requestAnimationFrame(frame);
}
function setupRing(el) {
if (el.getAttribute("data-ym-neon") === "1") return;
var atom = el.querySelector(".tn-atom") || el;
var pc = Math.max(0, Math.min(100, attr(el, "data-pc", CFG.pc)));
var rd = Math.max(0, attr(el, "data-rd", CFG.rd));
var bgPct = Math.max(0, Math.min(100, attr(el, "data-bg", CFG.bg)));
var speed = ringSpeed(el);
var trackA = bgPct / 100;
var cs = window.getComputedStyle(atom);
var borderColor = cs.borderTopColor || cs.borderColor || "#fff";
var rgb = parseRgb(borderColor);
var bw = parseFloat(cs.borderTopWidth || cs.borderWidth) || 4;
if (bw < 1) bw = 4;
var w = atom.offsetWidth || el.offsetWidth;
var h = atom.offsetHeight || el.offsetHeight;
var size = Math.min(w, h);
if (size < 8) return;
el.setAttribute("data-ym-neon", "1");
el.setAttribute("data-ym-neon-speed", String(speed));
el.classList.add("ym-neon-host");
if (window.getComputedStyle(el).position === "static") {
el.style.position = "relative";
}
atom.style.boxShadow = "none";
atom.style.webkitBoxShadow = "none";
el.classList.add("ym-neon-ready");
var old = el.querySelector(".ym-neon-svg");
if (old) old.remove();
var r = Math.max(1, (size - bw) / 2);
var c = 2 * Math.PI * r;
var off = c * (1 - pc / 100);
var svgNS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("class", "ym-neon-svg");
svg.setAttribute("viewBox", "0 0 " + size + " " + size);
svg.setAttribute("aria-hidden", "true");
svg.style.setProperty("--ym-neon-len", c.toFixed(2));
svg.style.setProperty("--ym-neon-off", off.toFixed(2));
svg.style.setProperty("--ym-neon-dur", speed + "s");
function circle(cls, width, stroke, extra) {
var n = document.createElementNS(svgNS, "circle");
n.setAttribute("class", cls);
n.setAttribute("cx", String(size / 2));
n.setAttribute("cy", String(size / 2));
n.setAttribute("r", String(r));
n.setAttribute("stroke-width", String(width));
n.setAttribute("stroke", stroke);
n.style.strokeDasharray = c.toFixed(2);
if (extra) {
Object.keys(extra).forEach(function (k) {
n.style[k] = extra[k];
});
}
svg.appendChild(n);
return n;
}
circle("ym-neon-track", bw, rgba(rgb, trackA), {
strokeDasharray: "none",
strokeDashoffset: "0"
});
var glowW = bw + Math.min(rd, 24) * 0.35;
var glowBlur = Math.max(2, rd * 0.45);
circle("ym-neon-glow", glowW, rgba(rgb, 0.75), {
filter: "blur(" + glowBlur + "px)",
opacity: "0.85"
});
circle("ym-neon-bar", bw, rgba(rgb, 1), {
filter:
"drop-shadow(0 0 " +
rd +
"px " +
rgba(rgb, 0.95) +
") drop-shadow(0 0 " +
rd * 0.45 +
"px " +
rgba(rgb, 0.7) +
")"
});
el.appendChild(svg);
}
function dist2(a, b) {
var ra = a.getBoundingClientRect();
var rb = b.getBoundingClientRect();
var dx = (ra.left + ra.right) / 2 - (rb.left + rb.right) / 2;
var dy = (ra.top + ra.bottom) / 2 - (rb.top + rb.bottom) / 2;
return dx * dx + dy * dy;
}
function animatesForRing(ring, block) {
var rings = findRings(block);
var anims = findAnimate(block);
if (!anims.length) return [];
if (rings.length <= 1) return anims;
return anims.filter(function (anim) {
var nearest = rings[0];
var best = dist2(anim, nearest);
for (var i = 1; i < rings.length; i++) {
var d = dist2(anim, rings[i]);
if (d < best) {
best = d;
nearest = rings[i];
}
}
return nearest === ring;
});
}
function playRing(ring, block) {
if (!ring || ring.getAttribute("data-ym-neon-on") === "1") return;
ring.setAttribute("data-ym-neon-on", "1");
if (ring.getAttribute("data-ym-neon") !== "1") setupRing(ring);
if (ring.getAttribute("data-ym-neon") !== "1") return;
ring.classList.add("is-on");
var speed = num(ring.getAttribute("data-ym-neon-speed"), ringSpeed(ring));
animatesForRing(ring, block).forEach(function (el) {
scrambleText(el, speed);
});
}
function observeRing(ring, block) {
function go() {
playRing(ring, block);
}
if (!("IntersectionObserver" in window)) {
go();
return;
}
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
go();
io.unobserve(e.target);
}
});
},
{ threshold: 0.35, rootMargin: "0px 0px -8% 0px" }
);
io.observe(ring);
}
function init() {
injectCss();
var blocks = findBlocks();
if (!blocks.length) return;
blocks.forEach(function (block) {
var rings = findRings(block);
rings.forEach(function (ring) {
setupRing(ring);
observeRing(ring, block);
});
});
}
var tries = 0;
function boot() {
var ready = findBlocks().some(function (b) {
return findRings(b).length > 0 || findAnimate(b).length > 0;
});
if (!ready && tries < 40) {
tries += 1;
setTimeout(boot, 120);
return;
}
init();
}
function start() {
if (typeof t_onFuncLoad === "function") {
t_onFuncLoad("t396_init", function () {
setTimeout(boot, 80);
});
} else {
boot();
}
}
if (typeof t_onReady === "function") {
t_onReady(start);
} else if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", start);
} else {
start();
}
window.YM_NEON_RINGS = CFG;
})();
</script>