Интерактивная блоб-кнопка

Модификатор для добавления интерактивного элемента (блоб)
Инструкция по настройке
Шаблон страницы
Как добавить шаблон
48473347
Для корректной работы модификаций подключите библиотеку jQuery:
Настройки сайта -> Еще -> Подключить jQuery на страницах сайта
jQuery
Создаем Zero Block и открываем редактор
  1. Добавляем кнопку и задаем ей ссылку
  2. Настраиваем ей размеры в соотношении 1к1
  3. Задаем кнопке цвет BG. COLOR ON HOVER (он будет заливать кнопку при наведении на нее курсора)
  4. В значение SHADOW задаем цвет для текста (такой цвет будет показываться при наведении)
  5. Задаем кнопке класс tistolsBtnBlobBase
1 шаг
Создаем блок T123 и копируем в него код
2 шаг
Скопировать код
<!-- 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>
Больше модификаций доступно по подписке
Полный доступ к 70+ модификациям
Обновления библиотеки
Оповещения о новых модификациях в канале
Техподдержка в чате
Поддержка модов при изменениях в Tilda
Модуль подарочные карты тариф Lite на месяц
Доступ к ИИ-боту @tistolsaibot на год
Помощь в доработке модификации
Поддержка модов при изменениях в Tilda
Техподдержка в чате
Модуль smsmod.ru на год
Сертификат на разработку 3 модификаций
Модуль подарочные карты тариф Pro на месяц
Оповещения о новых модификациях в канале
Обновления библиотеки
Полный доступ к 70+ модификациям
Оповещения о новых модификациях в канале
Обновления библиотеки
Полный доступ к 15+ модификациям
  • Открытый код и текстовые инструкции
  • Пошаговые видеоинструкции
  • Готовые шаблоны (страницы)
Free
0 руб.
Месяц
Год
Pro
3 000 руб.
-50%
Club
18 000 руб.
-40%
Месяц
Год
  • Открытый код и текстовые инструкции
  • Пошаговые видеоинструкции
  • Готовые шаблоны (страницы)
  • Модификации будут работать в проектах после истечения подписки
  • Открытый код и текстовые инструкции
  • Пошаговые видеоинструкции
  • Готовые шаблоны (страницы)
  • Модификации будут работать в проектах после истечения подписки
Полный доступ к 70+ модификациям
Обновления библиотеки
Оповещения о новых модификациях в канале
Техподдержка в чате
Поддержка модов при изменениях в Tilda
Модуль подарочные карты тариф Lite на месяц
Помощь в доработке модификации
Поддержка модов при изменениях в Tilda
Техподдержка в чате
Модуль smsmod.ru на месяц
Сертификат на разработку 1 модификаций
Модуль подарочные карты тариф Lite на месяц
Оповещения о новых модификациях в канале
Обновления библиотеки
Полный доступ к 70+ модификациям
Оповещения о новых модификациях в канале
Обновления библиотеки
Полный доступ к 15+ модификациям
  • Открытый код и текстовые инструкции
  • Пошаговые видеоинструкции
  • Готовые шаблоны (страницы)
Free
0 руб.
Месяц
Год
Pro
500 руб.
Club
2 500 руб.
Месяц
Год
  • Открытый код и текстовые инструкции
  • Пошаговые видеоинструкции
  • Готовые шаблоны (страницы)
  • Модификации будут работать в проектах после истечения подписки
  • Открытый код и текстовые инструкции
  • Пошаговые видеоинструкции
  • Готовые шаблоны (страницы)
  • Модификации будут работать в проектах после истечения подписки
Доступ к ИИ-боту @tistolsaibot на месяц
Вам также может быть интересно: