<!-- TL07 - Модификация для Тильды. Интерактивная блоб-кнопка https://mod.tistols.com/interactive-blob -->
<style>
.tistolsBtnBlob {
position: absolute;
transform: translate(-50%, -50%);
border-radius: 250px;
overflow: hidden;
pointer-events: none;
}
.tistolsBtnBlob-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.tistolsBtn_el {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
.btn__blob {
width: 100%;
height: 100%;
}
.btn__text {
display: block;
transition: all 0.6s ease;
z-index: 2;
pointer-events: none;
}
.tistolsBtnBlobBase {
/*position: relative;*/
overflow: visible !important;
cursor: pointer;
}
.tistolsBtnBlobBase .tn-atom {
opacity: 0 !important;
visibility: hidden !important;
pointer-events: none !important;
}
.tistolsBtnBlobBase .tn-atom__button-content {
display: none !important;
}
</style>
<script>
// Ждем загрузки DOM
document.addEventListener('DOMContentLoaded', function() {
// ========== КЛАССЫ ДЛЯ BLOB ЭФФЕКТА ==========
class Blob {
constructor(parCanvas) {
this.points = [];
this.parCanvas = parCanvas;
this._opacity = 1;
}
blobInit() {
for (let i = 0; i < this.numPoints; i++) {
let point = new Point(this.divisional * (i + 1), this);
point.acceleration = 0;
point.friction = 0.05;
this.push(point);
}
}
render() {
let canvas = this.canvas;
let ctx = this.ctx;
let position = this.position;
let pointsArray = this.points;
let radius = this.radius;
let points = this.numPoints;
let divisional = this.divisional;
let center = this.center;
ctx.clearRect(0, 0, canvas.width, canvas.height);
pointsArray[0].solveWith(pointsArray[points - 1], pointsArray[1]);
let p0 = pointsArray[points - 1].position;
let p1 = pointsArray[0].position;
let _p2 = p1;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
ctx.moveTo((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
for (let i = 1; i < points; i++) {
pointsArray[i].solveWith(pointsArray[i - 1], pointsArray[i + 1] || pointsArray[0]);
let p2 = pointsArray[i].position;
var xc = (p1.x + p2.x) / 2;
var yc = (p1.y + p2.y) / 2;
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc);
p1 = p2;
}
var xc = (p1.x + _p2.x) / 2;
var yc = (p1.y + _p2.y) / 2;
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc);
this.opacity = this.lerp(this.opacity, this.hover ? 1 : 1, 0.1);
ctx.globalAlpha = this.opacity;
ctx.fillStyle = this.color;
ctx.fill();
ctx.globalAlpha = 1;
ctx.lineWidth = 1;
ctx.strokeStyle = "#fff";
ctx.stroke();
requestAnimationFrame(this.render.bind(this));
}
lerp(start, end, amt) {
return (1 - amt) * start + amt * end;
}
push(item) {
if (item instanceof Point) {
this.points.push(item);
}
}
set color(value) {
this._color = value;
}
get color() {
return this._color || "#FCE600";
}
set hoverColor(value) {
this._hoverColor = value;
}
get hoverColor() {
return this._hoverColor || this.color;
}
set opacity(value) {
this._opacity = value;
}
get opacity() {
return this._opacity || 1;
}
set hover(value) {
this._hover = value;
// При наведении меняем цвет НА ЛЕТУ из оригинальной кнопки
if (value && this.getHoverColor) {
const hoverColor = this.getHoverColor();
if (hoverColor) {
this.color = hoverColor;
} else if (this._hoverColor) {
this.color = this._hoverColor;
}
} else if (!value && this._originalColor) {
this.color = this._originalColor;
}
}
get hover() {
return this._hover || false;
}
set canvas(value) {
if (value instanceof HTMLElement && value.tagName.toLowerCase() === "canvas") {
this._canvas = this.parCanvas;
this.ctx = this._canvas.getContext("2d");
}
}
get canvas() {
return this._canvas;
}
set numPoints(value) {
if (value > 2) {
this._points = value;
}
}
get numPoints() {
return this._points || 16;
}
set radius(value) {
if (value > 0) {
this._radius = value;
}
}
get radius() {
return this._radius || 150;
}
set position(value) {
if (typeof value === "object" && value.x && value.y) {
this._position = value;
}
}
get position() {
return this._position || {x: 0.5, y: 0.5};
}
get divisional() {
return (Math.PI * 2) / this.numPoints;
}
get center() {
return {
x: this.canvas.width * this.position.x,
y: this.canvas.height * this.position.y
};
}
set running(value) {
this._running = value === true;
}
get running() {
return this.running !== false;
}
// Метод для получения цвета hover на лету
getHoverColor() {
if (!this._getDynamicHoverColor || !this._originalLink) return null;
try {
const style = window.getComputedStyle(this._originalLink);
// 1. Пробуем CSS переменную
let hoverColor = style.getPropertyValue('--t396-bgcolor-hover-color').trim();
if (hoverColor && hoverColor !== '') return hoverColor;
// 2. Пробуем обычный background-color
hoverColor = style.backgroundColor;
if (hoverColor && hoverColor !== 'transparent') return hoverColor;
// 3. Пробуем box-shadow
if (style.boxShadow && style.boxShadow !== 'none') {
const match = style.boxShadow.match(/(#[0-9a-f]{3,6}|rgba?\([^)]+\))/i);
if (match) return match[0];
}
return null;
} catch (e) {
console.error('Error getting dynamic hover color:', e);
return null;
}
}
}
class Point {
constructor(azimuth, parent) {
this.parent = parent;
this.azimuth = Math.PI - azimuth;
this._components = {x: Math.cos(this.azimuth), y: Math.sin(this.azimuth)};
this.acceleration = -0.3 + Math.random() * 0.6;
}
solveWith(leftPoint, rightPoint) {
this.acceleration = (-0.3 * this.radialEffect + (leftPoint.radialEffect - this.radialEffect) + (rightPoint.radialEffect - this.radialEffect)) * this.elasticity -
this.speed * this.friction;
}
set acceleration(value) {
if (typeof value === "number") {
this._acceleration = value;
this.speed += this._acceleration * 2;
}
}
get acceleration() {
return this._acceleration || 0;
}
set speed(value) {
if (typeof value === "number") {
this._speed = value;
this.radialEffect += this._speed * 5;
}
}
get speed() {
return this._speed || 0;
}
set radialEffect(value) {
if (typeof value === "number") {
this._radialEffect = value;
}
}
get radialEffect() {
return this._radialEffect || 0;
}
get position() {
return {
x: this.parent.center.x +
this.components.x * (this.parent.radius + this.radialEffect),
y: this.parent.center.y +
this.components.y * (this.parent.radius + this.radialEffect)
};
}
get components() {
return this._components;
}
set elasticity(value) {
if (typeof value === "number") {
this._elasticity = value;
}
}
get elasticity() {
return this._elasticity || 0.001;
}
set friction(value) {
if (typeof value === "number") {
this._friction = value;
}
}
get friction() {
return this._friction || 0.0085;
}
}
class BlobButton {
constructor(container) {
this.container = container;
this.canvas = null;
this.textElement = null;
this.blob = null;
this.originalLink = null;
this.originalStyles = {};
this.init();
}
init() {
// Проверяем, инициализирована ли уже эта кнопка
if (this.container.dataset.blobInitialized === 'true') {
return;
}
// Находим оригинальную ссылку
this.originalLink = this.container.querySelector('a.tn-atom');
// Создаем структуру для blob эффекта
this.createBlobStructure();
// Находим элементы
this.canvas = this.container.querySelector('.btn__blob');
this.textElement = this.container.querySelector('.btn__text');
if (!this.canvas || !this.textElement) return;
// Получаем цвета из оригинальной кнопки
this.getOriginalColors();
// Инициализируем blob
this.blob = new Blob(this.canvas);
this.canvas.setAttribute("touch-action", "none");
// Сохраняем ссылку на оригинальный элемент для динамического получения цветов
this.blob._originalLink = this.originalLink;
this.blob._getDynamicHoverColor = true;
this.blobRect = this.container.getBoundingClientRect();
this.center = {x: 0, y: 0};
this.resize();
this.oldMousePoint = {x: 0, y: 0};
this.blob.canvas = this.canvas;
this.blob.radius = this.canvas.width / 2.4;
this.blob.speed = 999;
// Устанавливаем цвета
this.setColors();
this.addHoverListeners()
this.blob.blobInit();
this.blob.render();
// Добавляем обработчики для текста
// this.addTextHoverListeners();
// Помечаем кнопку как инициализированную
this.container.dataset.blobInitialized = 'true';
}
addHoverListeners() {
console.log('=== TILDA-STYLE HOVER COLOR ===');
// Как в оригинальном коде - берем цвет из box-shadow оригинальной кнопки
const getHoverColorFromBoxShadow = () => {
if (!this.originalLink) return null;
const style = window.getComputedStyle(this.originalLink);
const boxShadow = style.boxShadow;
console.log('Original box-shadow:', boxShadow);
if (boxShadow && boxShadow !== 'none') {
// Извлекаем цвет из box-shadow
// Формат: rgb(255, 255, 255) 0px 0px 0px 0px
const colorMatch = boxShadow.match(/^(rgb\([^)]+\)|rgba\([^)]+\)|#[0-9a-f]{3,6})/i);
if (colorMatch) {
return colorMatch[0];
}
}
return null;
};
// ИЛИ берем из CSS переменной
const getHoverColorFromCSSVar = () => {
if (!this.originalLink) return null;
const style = window.getComputedStyle(this.originalLink);
const cssVars = [
'--t396-color-hover',
'--color-hover',
'--hover-color',
'--t396-text-color-hover'
];
for (const cssVar of cssVars) {
const color = style.getPropertyValue(cssVar).trim();
if (color) {
return color;
}
}
return null;
};
// Пробуем разные источники по порядку
let hoverColor = getHoverColorFromCSSVar() ||
getHoverColorFromBoxShadow() ||
this.originalStyles.color ||
'#000000';
console.log('Hover color determined:', hoverColor);
console.log('For button:', this.textElement ? this.textElement.textContent : 'unknown');
this.hoverTextColor = hoverColor;
this.originalStyles.hoverColor = hoverColor;
}
// addTextHoverListeners() {
// // Только для текста - получаем динамические цвета
// this.container.addEventListener('mouseenter', () => {
// if (this.textElement) {
// this.textElement.classList.add('hover');
// // Пробуем получить hover цвет текста динамически
// try {
// if (this.originalLink) {
// const style = window.getComputedStyle(this.originalLink);
// let textHoverColor = style.getPropertyValue('--t396-color-hover').trim();
// if (!textHoverColor || textHoverColor === '') {
// textHoverColor = style.getPropertyValue('--t396-text-color-hover').trim();
// }
// if (textHoverColor && textHoverColor !== '') {
// this.textElement.style.color = textHoverColor;
// } else {
// // Fallback - цвет по умолчанию для hover
// this.textElement.style.color = '#000000';
// }
// }
// } catch (e) {
// console.error('Error getting text hover color:', e);
// this.textElement.style.color = '#000000';
// }
// }
// });
// this.container.addEventListener('mouseleave', () => {
// if (this.textElement) {
// this.textElement.classList.remove('hover');
// // Возвращаем оригинальный цвет текста
// if (this.originalStyles && this.originalStyles.color) {
// this.textElement.style.color = this.originalStyles.color;
// }
// }
// });
// }
getOriginalColors() {
if (!this.originalLink) {
// Значения по умолчанию
this.originalStyles = {
// backgroundColor: 'transparent', // прозрачный по умолчанию
hoverBackgroundColor: '#FCE600',
color: '#ffffff',
hoverColor: '#000000'
};
return;
}
// Получаем computed styles
const computedStyle = window.getComputedStyle(this.originalLink);
// 1. Основной цвет фона - БЕРЕМ ИЗ CSS ПЕРЕМЕННОЙ --t396-bgcolor-color
let backgroundColor = computedStyle.getPropertyValue('--t396-bgcolor-color').trim();
// Если CSS переменная пустая или не задана, оставляем прозрачный
if (!backgroundColor || backgroundColor === '' || backgroundColor === 'initial') {
backgroundColor = 'transparent';
}
this.originalStyles.backgroundColor = backgroundColor;
// 2. Цвет фона при наведении - БЕРЕМ ИЗ CSS ПЕРЕМЕННОЙ --t396-bgcolor-hover-color
let hoverBgColor = computedStyle.getPropertyValue('--t396-bgcolor-hover-color').trim();
if (!hoverBgColor || hoverBgColor === '' || hoverBgColor === 'initial') {
// Если нет hover цвета, пробуем box-shadow (старый метод)
if (computedStyle.boxShadow && computedStyle.boxShadow !== 'none') {
const colorMatch = computedStyle.boxShadow.match(/^(rgb\([^)]+\)|rgba\([^)]+\)|#[0-9a-f]{3,6})/i);
if (colorMatch) {
hoverBgColor = colorMatch[0];
}
}
// Если все еще нет, используем fallback
if (!hoverBgColor || hoverBgColor === '') {
hoverBgColor = '#FCE600';
}
}
this.originalStyles.hoverBackgroundColor = hoverBgColor;
// 3. Основной цвет текста
this.originalStyles.color = computedStyle.color;
// 4. Цвет текста при наведении
let hoverTextColor = computedStyle.getPropertyValue('--t396-color-hover').trim();
if (!hoverTextColor || hoverTextColor === '') {
hoverTextColor = computedStyle.getPropertyValue('--t396-text-color-hover').trim();
}
if (!hoverTextColor || hoverTextColor === '') {
hoverTextColor = '#000000';
}
this.originalStyles.hoverColor = hoverTextColor;
// 5. Другие свойства
this.originalStyles.href = this.originalLink.getAttribute('href');
this.originalStyles.target = this.originalLink.getAttribute('target');
console.log('=== BUTTON COLORS ===');
console.log('Background (--t396-bgcolor-color):', this.originalStyles.backgroundColor);
console.log('Hover background (--t396-bgcolor-hover-color):', this.originalStyles.hoverBackgroundColor);
console.log('Text color:', this.originalStyles.color);
console.log('Hover text color:', this.originalStyles.hoverColor);
console.log('================================');
// Делаем оригинальную ссылку невидимой
this.originalLink.style.opacity = '0';
this.originalLink.style.pointerEvents = 'none';
this.originalLink.style.visibility = 'hidden';
}
getHoverColor(element, property) {
// Используем оптимизированный метод для получения hover цвета
return this.getHoverColorSimple(element, property);
}
getHoverColorSimple(element, property) {
try {
// Быстрый метод без создания временных элементов
const computedStyle = window.getComputedStyle(element);
// Пробуем получить CSS переменные для hover состояния
const hoverColor = computedStyle.getPropertyValue(`--t396-${property}-hover`).trim();
if (hoverColor && hoverColor !== '') return hoverColor;
// Для фона проверяем box-shadow
if (property === 'background-color' && computedStyle.boxShadow !== 'none') {
const match = computedStyle.boxShadow.match(/(#[0-9a-f]{3,6}|rgba?\([^)]+\))/i);
if (match) return match[0];
}
// Возвращаем текущий цвет как fallback
return computedStyle[property];
} catch (e) {
console.error('Error getting hover color:', e);
return null;
}
}
createBlobStructure() {
// Проверяем, есть ли уже структура
if (this.container.querySelector('.tistolsBtnBlob')) {
return;
}
// Получаем текст из оригинальной кнопки
let buttonText = 'Button';
if (this.originalLink) {
const textSpan = this.originalLink.querySelector('.tn-atom__button-text');
if (textSpan) {
buttonText = textSpan.textContent || 'Button';
}
}
// Определяем размеры
const containerWidth = this.container.offsetWidth || 320;
const containerHeight = this.container.offsetHeight || 320;
const blobSize = Math.max(containerWidth, containerHeight);
// Создаем HTML структуру blob кнопки
const blobHTML = `
<div class="tistolsBtnBlob" style="
left: ${containerWidth / 2}px;
top: ${containerHeight / 2}px;
width: ${blobSize}px;
height: ${blobSize}px;">
<div class="tistolsBtnBlob-wrapper">
<canvas class="tistolsBtn_el btn__blob"
width="${blobSize}"
height="${blobSize}">
</canvas>
<span class="tistolsBtn_el btn__text">${buttonText}</span>
</div>
</div>
`;
// Добавляем структуру в контейнер
this.container.insertAdjacentHTML('beforeend', blobHTML);
// Делаем контейнер кликабельным
this.container.style.cursor = 'pointer';
// this.container.style.position = 'relative';
this.container.style.overflow = 'visible';
// Добавляем обработчик клика
this.container.addEventListener('click', (e) => {
if (this.originalStyles && this.originalStyles.href && this.originalStyles.href !== '#') {
if (this.originalStyles.target === '_blank') {
window.open(this.originalStyles.href, '_blank');
} else {
window.location.href = this.originalStyles.href;
}
}
e.preventDefault();
});
}
setColors() {
if (!this.blob || !this.originalStyles) return;
console.log('=== SETTING BLOB COLORS (TRANSPARENT SUPPORT) ===');
// 1. Получаем цвет из оригинальной кнопки
let backgroundColor = null;
let hoverColor = null;
if (this.originalLink) {
const computedStyle = window.getComputedStyle(this.originalLink);
// Фоновый цвет
backgroundColor = computedStyle.backgroundColor;
// Проверяем на прозрачность
const isTransparent = backgroundColor === 'transparent' ||
backgroundColor === 'rgba(0, 0, 0, 0)';
// Если прозрачный, можно оставить как есть или установить легкий фон
if (isTransparent) {
// Вариант 1: Оставляем почти прозрачный
backgroundColor = 'rgba(252, 230, 0, 0)'; // Слегка желтый
// Вариант 2: Берем цвет из других источников
const cssColor = computedStyle.getPropertyValue('--t396-bgcolor-color').trim();
if (cssColor && cssColor !== '' && cssColor !== 'initial') {
backgroundColor = cssColor;
}
}
// Hover цвет
hoverColor = computedStyle.getPropertyValue('--t396-bgcolor-hover-color').trim();
if (!hoverColor || hoverColor === '') {
// Из box-shadow
if (computedStyle.boxShadow !== 'none') {
const match = computedStyle.boxShadow.match(/^(rgb\([^)]+\)|rgba\([^)]+\)|#[0-9a-f]{3,6})/i);
if (match) hoverColor = match[0];
}
}
}
// 2. Устанавливаем дефолты если цвета не определены
if (!backgroundColor || backgroundColor === 'transparent') {
backgroundColor = '#FCE600';
}
if (!hoverColor || hoverColor === '') {
hoverColor = '#000000';
}
console.log('Blob color:', backgroundColor);
console.log('Hover color:', hoverColor);
// 3. Устанавливаем цвета
this.blob._originalColor = backgroundColor;
this.blob.hoverColor = hoverColor;
this.blob.color = backgroundColor;
// 4. Устанавливаем стили текста (оставляем ваш существующий код)
if (this.textElement && this.originalLink) {
const computedStyle = window.getComputedStyle(this.originalLink);
this.textElement.style.fontFamily = computedStyle.fontFamily;
this.textElement.style.fontSize = computedStyle.fontSize;
this.textElement.style.fontWeight = computedStyle.fontWeight;
this.textElement.style.fontStyle = computedStyle.fontStyle;
this.textElement.style.lineHeight = computedStyle.lineHeight;
this.textElement.style.letterSpacing = computedStyle.letterSpacing;
this.textElement.style.textTransform = computedStyle.textTransform;
this.textElement.style.color = this.originalStyles.color || '#ffffff';
console.log('Text styles applied');
}
}
// Добавьте этот метод для получения цвета из Tilda
getButtonColorFromTilda() {
if (!this.originalLink) return null;
try {
const style = window.getComputedStyle(this.originalLink);
// 1. Пробуем CSS переменную
let color = style.getPropertyValue('--t396-bgcolor-color').trim();
if (color && color !== '' && color !== 'initial') return color;
// 2. Пробуем background-color
color = style.backgroundColor;
if (color && color !== 'transparent' && color !== 'rgba(0, 0, 0, 0)') {
return color;
}
// 3. Пробуем фон из псевдоэлемента (если есть градиент)
if (style.backgroundImage && style.backgroundImage !== 'none') {
return style.backgroundImage;
}
return null;
} catch (e) {
console.error('Error getting Tilda color:', e);
return null;
}
}
resize() {
if (!this.canvas) return;
const {width, height, top, left} = this.canvas.getBoundingClientRect();
const containerRect = this.container.getBoundingClientRect();
this.center.x = left - containerRect.left + width / 2;
this.center.y = top - containerRect.top + height / 2;
this.canvas.width = width;
this.canvas.height = height;
}
mouseMove(e) {
if (!this.blob || !this.container) return;
const containerRect = this.container.getBoundingClientRect();
const pos = {
x: e.clientX - containerRect.left,
y: e.clientY - containerRect.top
};
const diff = {
x: pos.x - this.center.x,
y: pos.y - this.center.y
};
const dist = Math.sqrt(diff.x * diff.x + diff.y * diff.y);
let angle = null;
this.blob.mousePos = {
x: this.center.x - pos.x,
y: this.center.y - pos.y
};
// Определяем hover состояние
const wasHovered = this.blob.hover;
const isNowHovered = dist < this.blob.radius;
if (isNowHovered && !wasHovered) {
// Начало наведения
const vector = {x: pos.x - this.center.x, y: pos.y - this.center.y};
angle = Math.atan2(vector.y, vector.x);
this.blob.hover = true;
// Меняем цвет текста
if (this.textElement && this.hoverTextColor) {
this.textElement.style.color = this.hoverTextColor;
this.textElement.classList.add('hover');
console.log('Text color changed to hover:', this.hoverTextColor);
}
} else if (!isNowHovered && wasHovered) {
// Конец наведения
const vector = {x: pos.x - this.center.x, y: pos.y - this.center.y};
angle = Math.atan2(vector.y, vector.x);
this.blob.hover = false;
// Восстанавливаем цвет текста
if (this.textElement) {
this.textElement.style.color = this.originalStyles.color;
this.textElement.classList.remove('hover');
console.log('Text color restored to:', this.originalStyles.color);
}
}
if (typeof angle === "number") {
let nearestPoint = null;
let distanceFromPoint = 100;
this.blob.points.forEach((point) => {
if (Math.abs(angle - point.azimuth) < distanceFromPoint) {
nearestPoint = point;
distanceFromPoint = Math.abs(angle - point.azimuth);
}
});
if (nearestPoint) {
let strength = {
x: this.oldMousePoint.x - pos.x,
y: this.oldMousePoint.y - pos.y
};
strength = Math.sqrt(strength.x * strength.x + strength.y * strength.y) * 10;
if (strength > 100) strength = 100;
nearestPoint.acceleration = (strength / 100) * (this.blob.hover ? -1 : 1);
}
}
this.oldMousePoint.x = pos.x;
this.oldMousePoint.y = pos.y;
}
}
// ========== ИНИЦИАЛИЗАЦИЯ ВСЕХ BLOB КНОПОК ==========
let blobButtons = [];
function initBlobButtons() {
// Находим все кнопки с классом tistolsBtnBlobBase
const blobContainers = document.querySelectorAll('.tistolsBtnBlobBase');
blobContainers.forEach(container => {
// Пропускаем уже инициализированные кнопки
if (container.dataset.blobInitialized === 'true') {
// Находим существующий экземпляр
const existingButton = blobButtons.find(btn => btn.container === container);
if (!existingButton) {
// Создаем новый экземпляр
const blobButton = new BlobButton(container);
blobButtons.push(blobButton);
container.blobButton = blobButton;
}
return;
}
// Создаем экземпляр blob кнопки
const blobButton = new BlobButton(container);
blobButtons.push(blobButton);
// Сохраняем ссылку на экземпляр
container.blobButton = blobButton;
});
console.log(`Инициализировано ${blobButtons.length} blob кнопок`);
}
// Глобальные обработчики событий
function handleResize() {
blobButtons.forEach(button => {
if (button && button.resize) {
button.resize();
}
});
}
function handleMouseMove(e) {
blobButtons.forEach(button => {
if (button && button.mouseMove) {
button.mouseMove(e);
}
});
}
// Инициализация при загрузке
setTimeout(() => {
initBlobButtons();
}, 100);
// Добавляем обработчики событий
window.addEventListener('resize', handleResize);
window.addEventListener('pointermove', handleMouseMove);
// Обработка динамически добавленных элементов
const observer = new MutationObserver((mutations) => {
let shouldReinit = false;
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
// Проверяем, является ли элемент blob кнопкой
if (node.classList && node.classList.contains('tistolsBtnBlobBase')) {
shouldReinit = true;
}
// Проверяем дочерние элементы
if (node.querySelector && node.querySelector('.tistolsBtnBlobBase')) {
shouldReinit = true;
}
}
});
}
// Проверяем изменения атрибутов
if (mutation.type === 'attributes' &&
mutation.attributeName === 'class' &&
mutation.target.classList.contains('tistolsBtnBlobBase')) {
shouldReinit = true;
}
});
if (shouldReinit) {
setTimeout(() => {
initBlobButtons();
}, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
// ========== ПУБЛИЧНЫЙ API ДЛЯ УПРАВЛЕНИЯ ==========
window.TildaBlobButtons = {
// Переинициализировать все кнопки
reinit: function() {
blobButtons.forEach(button => {
if (button && button.container) {
delete button.container.dataset.blobInitialized;
}
});
blobButtons = [];
initBlobButtons();
},
// Инициализировать конкретный элемент как blob кнопку
initElement: function(element) {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
// Добавляем класс если его нет
if (!element.classList.contains('tistolsBtnBlobBase')) {
element.classList.add('tistolsBtnBlobBase');
}
// Создаем blob кнопку
const blobButton = new BlobButton(element);
blobButtons.push(blobButton);
element.blobButton = blobButton;
return blobButton;
},
// Получить цвета для конкретной кнопки
getButtonColors: function(element) {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element || !element.blobButton) return null;
return element.blobButton.originalStyles;
},
// Обновить цвета кнопки
updateButtonColors: function(element, colors) {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element || !element.blobButton) return;
Object.assign(element.blobButton.originalStyles, colors);
element.blobButton.setColors();
},
// Получить все blob кнопки
getAllButtons: function() {
return blobButtons;
},
// Удалить blob эффект с кнопки
removeFromElement: function(element) {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return;
// Удаляем структуру blob
const blobElement = element.querySelector('.tistolsBtnBlob');
if (blobElement) {
blobElement.remove();
}
// Восстанавливаем оригинальную кнопку
const originalLink = element.querySelector('a.tn-atom');
if (originalLink) {
originalLink.style.opacity = '';
originalLink.style.pointerEvents = '';
originalLink.style.visibility = '';
}
// Удаляем класс
element.classList.remove('tistolsBtnBlobBase');
// Удаляем из массива
const index = blobButtons.findIndex(btn => btn.container === element);
if (index > -1) {
blobButtons.splice(index, 1);
}
// Удаляем ссылку
delete element.blobButton;
delete element.dataset.blobInitialized;
}
};
});
</script>