<!-- TL07 - Модификация для Тильды. Интерактивная блоб-кнопка https://mod.tistols.com/interactive-blob -->
<style>
.tistolsBtnBlob {
position: absolute;
transform: translate(-50%, -50%);
width: 300px;
height: 300px;
border-radius: 250px;
}
.tistolsBtnBlob .tistolsBtnBlob-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.tistolsBtnBlob .tistolsBtn_el {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.tistolsBtnBlob .btn__blob {
width: 100%;
height: 100%;
}
.tistolsBtnBlob .btn__text {
display: block;
color: #fff;
transition: all 0.6s ease;
font-family: 'Inter', Arial, sans-serif;
}
.tistolsBtnBlob .btn__text.hover {
color: #000;
}</style>
<script>
class Blob {
constructor(parCanvas) {
this.points = [];
this.parCanvas = parCanvas;
}
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 : 0, 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 opacity(value) {
this._opacity = value;
}
get opacity() {
return this._opacity || 0;
}
set hover(value) {
this._hover = value;
}
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;
}
}
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(divBase) {
this.canvas = $(divBase).find("canvas")[0];
this.objText = $(divBase).find("span")[0];
this.blob = new Blob(this.canvas);
this.init(divBase);
}
init(divBase) {
this.canvas.setAttribute("touch-action", "none");
this.blobContainer = divBase;
this.blobRect = this.blobContainer.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.blob.blobInit();
this.blob.render();
}
resize() {
const {width, height, top, left} = this.canvas.getBoundingClientRect();
this.center.x = left - this.blobRect.left + width / 2;
this.center.y = top - this.blobRect.top + height / 2;
this.canvas.width = width;
this.canvas.height = height;
}
mouseMove(e) {
let blobRect = this.blobContainer.getBoundingClientRect();
let pos = {x: e.clientX - blobRect.left, y: e.clientY - blobRect.top};
let diff = {x: pos.x - this.center.x, y: pos.y - this.center.y};
let 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};
if (dist < this.blob.radius && this.blob.hover === false) {
let vector = {x: pos.x - this.center.x, y: pos.y - this.center.y};
angle = Math.atan2(vector.y, vector.x);
this.blob.hover = true;
this.objText.classList.add("hover");
} else if (dist > this.blob.radius && this.blob.hover === true) {
let vector = {x: pos.x - this.center.x, y: pos.y - this.center.y};
angle = Math.atan2(vector.y, vector.x);
this.blob.hover = false;
this.objText.classList.remove("hover");
}
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;
}
}
const buttonsBlob = document.getElementsByClassName('tistolsBtnBlobBase');
for (let i = 0; i < buttonsBlob.length; i++) {
const btn = buttonsBlob[i];
const blobBaseW = $(btn).width();
const blobBaseH = $(btn).height();
const blobCircleD = Math.max(blobBaseW, blobBaseH);
const objInner = $(btn).children().first();
const sText = objInner.html();
objInner.html("<div class=\"tistolsBtnBlob\" style=\"left: " + blobBaseW / 2 + "px; width: " + blobCircleD + "px; height: " + blobCircleD + "px; \"><div class=\"tistolsBtnBlob-wrapper\">" +
"<canvas class=\"tistolsBtn_el btn__blob\" touch-action=\"none\" width=\"300\" height=\"300\"></canvas>" +
"<span class=\"tistolsBtn_el btn__text\">" + sText + "</span></div></div>");
btn.blobButton = new BlobButton(btn);
const objA = $(btn).children("a");
btn.blobButton.blob.color = objA.css("background-color");
objA.css("background-color", "transparent");
$(btn).find("span").css("color", objA.css("color"));
$(btn).find(".tistolsBtnBlob").hover(function(){
$(this).find("span").css('color', objA.css('box-shadow').replace(/^.*(rgba?\([^)]+\)).*$/,'$1'));
}, function(){
$(this).find("span").css('color', objA.css("color"));
});
}
resize = function () {
for (let i = 0; i < buttonsBlob.length; i++) {
buttonsBlob[i].blobButton.resize();
}
}
window.addEventListener("resize", resize);
mouseMove = function (e) {
for (let i = 0; i < buttonsBlob.length; i++) {
buttonsBlob[i].blobButton.mouseMove(e);
}
}
window.addEventListener("pointermove", mouseMove);
</script>