window.addEventListener("load", function () { var canvas = document.querySelector("#canvas_screen_cursor"); if (!canvas) return; let w_width = window.innerWidth; let w_height = window.innerHeight; canvas.width = w_width; canvas.height = w_height; let ctx = canvas.getContext("2d"); // Single render loop. It runs only while a cursor is still moving or easing, // and restarts on any pointer, hover or scroll activity. let cursor_running = false; function tick() { ctx.clearRect(0, 0, w_width, w_height); let moving = false; cursor_circles.forEach((cursor) => { // Two steps per frame keeps the same follow speed as before. if (cursor.step()) moving = true; if (cursor.step()) moving = true; cursor.draw(); }); if (moving) { window.requestAnimationFrame(tick); } else { cursor_running = false; } } function wake_cursor() { if (cursor_running || !cursor_circles.length) return; cursor_running = true; window.requestAnimationFrame(tick); } let cursor_effect_btn = $( `.slider1-item,.slider-control-arrow,a,[cursor-effect],.client-block,.crew-cms-img-contain,.button`, ); cursor_effect_btn.on("mouseenter", function () { mouse_info.down = true; mouse_info.up = false; wake_cursor(); }); cursor_effect_btn.on("mouseleave", function () { mouse_info.down = false; mouse_info.up = true; wake_cursor(); }); $("[cursor-arrow-mode]").on("mouseenter", function () { mouse_info.arrow_mode.active = true; wake_cursor(); }); $("[cursor-arrow-mode]").on("mouseleave", function () { mouse_info.arrow_mode.active = false; wake_cursor(); }); $("body").on("mousedown", function () { mouse_info.down = true; mouse_info.up = false; wake_cursor(); }); window.addEventListener("mousemove", function (e) { if (mouse_info.slide_dragging) return; mouse_info.x = e.clientX; mouse_info.y = e.clientY; wake_cursor(); }); setTimeout(() => { if (typeof flkty !== "undefined" && flkty) { flkty.on("dragMove", function (e) { mouse_info.x = e.clientX; mouse_info.y = e.clientY; wake_cursor(); }); flkty.on("dragStart", function () { mouse_info.slide_dragging = true; }); flkty.on("dragEnd", function () { mouse_info.slide_dragging = false; wake_cursor(); }); } }, 2000); window.addEventListener("mouseup", function () { mouse_info.up = true; mouse_info.down = false; wake_cursor(); }); ["mouseover", "mouseout", "wheel", "scroll"].forEach((ev) => window.addEventListener(ev, wake_cursor, { passive: true }), ); window.addEventListener("resize", function () { w_width = window.innerWidth; w_height = window.innerHeight; ctx.clearRect(0, 0, w_width, w_height); canvas.width = w_width; canvas.height = w_height; wake_cursor(); }); class Cursor { constructor(x, y, radius, speed, type, config) { this.x = x; this.y = y; this.radius = radius; this.max_radius = type === "inner" ? radius * 2 : radius * 3; this.min_radius = radius; this.color = config.color; this.img = config.img; this.speed = speed; this.type = type; this.easing_value = type === "inner" ? 0.05 : 0.2; this.opacity = 1; this.rotationAngle = 0; this.prevRotationAngle = 0; if (this.img) this.img.addEventListener("load", wake_cursor); } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); switch (this.type) { case "inner": if (mouse_info.arrow_mode.active) { this.drawImageWithRotation(); } else { ctx.fillStyle = `rgba(${this.color},${ mouse_info.down ? 0.5 * this.opacity : 1 * this.opacity })`; ctx.fill(); } break; case "outer": ctx.strokeStyle = `rgba(${this.color},${ mouse_info.arrow_mode.active ? 1 * this.opacity : 0 * this.opacity })`; if (mouse_info.arrow_mode.active) { ctx.fillStyle = `rgba(${this.color},${1 * this.opacity})`; ctx.fill(); } if (mouse_info.down && mouse_info.arrow_mode.active) { ctx.strokeStyle = `rgba(${this.color},${0.5 * this.opacity})`; } ctx.stroke(); break; default: } } updateImg(newImgSrc) { this.img.src = newImgSrc; wake_cursor(); } updateOpacity(newOpacityValue) { this.opacity = newOpacityValue; wake_cursor(); } drawImageWithRotation() { const size = 20; ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.prevRotationAngle); ctx.drawImage(this.img, -size / 2, -size / 2, size, size); ctx.rotate(this.rotationAngle - this.prevRotationAngle); ctx.drawImage(this.img, -size / 2, -size / 2, size, size); ctx.restore(); } rotate_arrow() { const targetAngle = mouse_info.arrow_mode.screen_side === "left" ? 0 : Math.PI; const angleDiff = targetAngle - this.rotationAngle; this.prevRotationAngle = this.rotationAngle; this.rotationAngle += angleDiff / 10; return Math.abs(angleDiff) > 0.001; } update_inner_radius() { if (!mouse_info.arrow_mode.active) { this.min_radius = 4; this.max_radius = 16; } if (mouse_info.down && this.radius < this.max_radius) { this.radius += 0.001; this.radius += (this.max_radius - this.radius) * this.easing_value; } if (mouse_info.up && this.radius > this.min_radius) { this.radius -= 0.001; this.radius += (this.min_radius - this.radius) * this.easing_value; } } update_outer_radius() { if (mouse_info.arrow_mode.active) { this.min_radius = 24; this.max_radius = 28; } else { this.min_radius = 0.01; this.max_radius = 24; } if (mouse_info.down || !mouse_info.arrow_mode.active) { this.decrease_radius(); } if (mouse_info.up || mouse_info.arrow_mode.active) { this.increase_radius(); } } decrease_radius() { if (this.radius > this.min_radius) { this.radius -= 0.001; this.radius += (this.min_radius - this.radius) * this.easing_value; } } increase_radius() { if (this.radius < this.max_radius) { this.radius += 0.001; this.radius += (this.max_radius - this.radius) * this.easing_value; } } move_cursor() { let dx = mouse_info.x - this.x; let dy = mouse_info.y - this.y; let distance = Math.sqrt(dx * dx + dy * dy); let angle = Math.atan2(dy, dx); let max_speed = distance / this.speed; this.x += max_speed * Math.cos(angle); this.y += max_speed * Math.sin(angle); return distance > 0.1; } // Advances the cursor one step. Returns true while it is still changing. step() { let current_cursor_position = this.x < canvas.width / 2 ? "left" : "right"; if (mouse_info.arrow_mode.screen_side !== current_cursor_position) { mouse_info.arrow_mode.screen_side = current_cursor_position; } const prev_radius = this.radius; switch (this.type) { case "inner": this.update_inner_radius(); break; case "outer": this.update_outer_radius(); break; default: } let changing = this.move_cursor(); if (Math.abs(this.radius - prev_radius) > 0.0001) changing = true; if (this.type === "inner" && mouse_info.arrow_mode.active && this.rotate_arrow()) { changing = true; } return changing; } update() { this.step(); this.draw(); } } let cursor_color; let preset_cursor_color = $("body").attr("cursor-color"); if (preset_cursor_color) { cursor_color = preset_cursor_color; } else { cursor_color = "107, 119, 128"; } function initCanvas() { if (windowViewPort === "desktop") { let cursor_arrow_img = new Image(); cursor_arrow_img.src = "https://uploads-ssl.webflow.com/62bde5720d5cbfc1dd34dae0/64a3c577f2b9dad513495060_closing%20icon.png"; cursor_inner = new Cursor(8, 8, 4, 6, "inner", { img: cursor_arrow_img, color: cursor_color, }); cursor_outer = new Cursor(8, 8, 16, 8, "outer", { color: cursor_color, }); cursor_circles.push(cursor_outer); cursor_circles.push(cursor_inner); } wake_cursor(); } setTimeout(() => { initCanvas(); }, 500); });