Interactive mouse effect generating sparks with physically realistic behavior. Made with jQuery.
A Pen by Nazar Kyselov on CodePen.
Interactive mouse effect generating sparks with physically realistic behavior. Made with jQuery.
A Pen by Nazar Kyselov on CodePen.
| <body> | |
| <!-- Start fire glows --> | |
| <!-- This doesn't relate to any functionality. Just for prettiness --> | |
| <div class="fire-glow fire-glow-1"> | |
| <div class="container"> | |
| <span class="inner-glow inner-glow-1"></span> | |
| <span class="inner-glow inner-glow-2"></span> | |
| <span class="inner-glow inner-glow-3"></span> | |
| </div> | |
| </div> | |
| <div class="fire-glow fire-glow-2"> | |
| <div class="container"> | |
| <span class="inner-glow inner-glow-1"></span> | |
| <span class="inner-glow inner-glow-2"></span> | |
| <span class="inner-glow inner-glow-3"></span> | |
| </div> | |
| </div> | |
| <div class="fire-glow fire-glow-3"> | |
| <div class="container"> | |
| <span class="inner-glow inner-glow-1"></span> | |
| <span class="inner-glow inner-glow-2"></span> | |
| <span class="inner-glow inner-glow-3"></span> | |
| </div> | |
| </div> | |
| <div class="fire-glow fire-glow-4"> | |
| <div class="container"> | |
| <span class="inner-glow inner-glow-1"></span> | |
| <span class="inner-glow inner-glow-2"></span> | |
| <span class="inner-glow inner-glow-3"></span> | |
| </div> | |
| </div> | |
| <div class="fire-glow fire-glow-5"> | |
| <div class="container"> | |
| <span class="inner-glow inner-glow-1"></span> | |
| <span class="inner-glow inner-glow-2"></span> | |
| <span class="inner-glow inner-glow-3"></span> | |
| </div> | |
| </div> | |
| <div class="fire-glow fire-glow-6"> | |
| <div class="container"> | |
| <span class="inner-glow inner-glow-1"></span> | |
| <span class="inner-glow inner-glow-2"></span> | |
| <span class="inner-glow inner-glow-3"></span> | |
| </div> | |
| </div> | |
| <!-- End fire glows --> | |
| <!-- Include jQuery with CDN --> | |
| <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> | |
| <script src="main.js"></script> | |
| </body> |
| const config = { | |
| colors: [ | |
| // spark possible colors | |
| "#eddc01", | |
| "#f2b125", | |
| "#fd9407", | |
| "#ff7308", | |
| "#eb5508", | |
| "#fe1a17", | |
| "#e93702" | |
| ], | |
| sizes: [4, 6, 8], // diameter in px | |
| minimalDistance: 20, // minimal distance between spawned | |
| gravitation: 0.2, | |
| airResistance: 0.98, | |
| shrink: 0.1 | |
| }; | |
| //? store coordinates of the prev and last generated spark | |
| var prev = { x: 0, y: 0 }, | |
| last = { x: 0, y: 0 }; | |
| //? cash frequantly used elements | |
| const $body = $("body"); | |
| const $document = $(document); | |
| const appendElement = (el) => $body.append(el), | |
| removeElement = (el) => setTimeout(() => $(el).remove()); | |
| //? pick random element in defined range from array | |
| const rand = (min, max) => Math.floor(Math.random() * (max - min + 1) + min), | |
| pickRandom = (arr) => arr[rand(0, arr.length - 1)]; | |
| const calcDistance = (a, b) => | |
| Math.floor(Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)); | |
| const calcAngleRadians = (startPoint, endPoint) => | |
| Math.atan2(startPoint.y - endPoint.y, endPoint.x - startPoint.x); | |
| const updateCoords = (position) => { | |
| //? update position of prev generated spark | |
| prev.x = last.x; | |
| prev.y = last.y; | |
| //? and the last spark | |
| last.x = position.x; | |
| last.y = position.y; | |
| }; | |
| const generateSpeed = () => { | |
| var angle = (Math.random() * 360 * Math.PI) / 180; //? random angle in radians | |
| var speed = Math.random() * 2 + 1; //? random speed | |
| //? additional speed that depends on the speed and direction of mouse movement | |
| var addSpeed = (calcDistance(prev, last) / config.minimalDistance) * 2; | |
| var addSpeedY = addSpeed * Math.sin(calcAngleRadians(prev, last)); | |
| var addSpeedX = addSpeed * Math.cos(calcAngleRadians(prev, last)); | |
| //? output X and Y axis speeds | |
| var speedY = speed * Math.sin(angle) + addSpeedY; | |
| var speedX = speed * Math.cos(angle) + addSpeedX; | |
| return { speedX, speedY }; | |
| }; | |
| const animateSpark = (spark) => { | |
| //? constants for physically realistic animation | |
| var { speedX, speedY } = generateSpeed(); | |
| const gravitation = config.gravitation; | |
| const airResistance = config.airResistance; | |
| const shrink = config.shrink; | |
| function animate() { | |
| spark.css({ | |
| top: "-=" + speedY, | |
| left: "+=" + speedX, | |
| width: "-=" + shrink, | |
| height: "-=" + shrink | |
| }); | |
| //? gravitation imitation | |
| speedY -= gravitation; | |
| //? air resistance imitation | |
| speedX *= airResistance; | |
| //? if a spark is beyond the page or its size is smaller than 0 | |
| if ( | |
| $(spark).top > $(window).height || | |
| $(spark).left < 0 || | |
| $(spark).width() <= 0 | |
| ) { | |
| removeElement(spark); | |
| } else { | |
| requestAnimationFrame(animate); | |
| } | |
| } | |
| animate(); | |
| }; | |
| const createSpark = (position) => { | |
| const spark = $("<div></div>"); | |
| const size = pickRandom(config.sizes); | |
| spark.addClass("spark"); | |
| spark.append(`<div class="spark-glow"></div>`); | |
| $(spark).css({ | |
| left: position.x, | |
| top: position.y, | |
| background: pickRandom(config.colors), | |
| width: size, | |
| height: size | |
| }); | |
| animateSpark(spark); | |
| appendElement(spark); | |
| updateCoords(position); | |
| }; | |
| $document.mousemove((e) => { | |
| const position = { | |
| x: e.clientX, | |
| y: e.clientY | |
| }; | |
| if (calcDistance(last, position) > config.minimalDistance) | |
| createSpark(position); | |
| }); |
| /* ========= Reset styles ========== */ | |
| * { | |
| padding: 0; | |
| margin: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| height: 100vh; | |
| background: linear-gradient(to bottom, #1a0001, #270002, #380103, #4c0101); | |
| display: flex; | |
| align-items: flex-end; | |
| justify-content: center; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| /* ========= Fire-glows ========== */ | |
| .fire-glow { | |
| position: absolute; | |
| filter: blur(100px); | |
| } | |
| .fire-glow-1 { | |
| width: 700px; | |
| height: 700px; | |
| left: -200px; | |
| bottom: -400px; | |
| opacity: 0.5; | |
| } | |
| .fire-glow-2 { | |
| width: 800px; | |
| height: 800px; | |
| right: -200px; | |
| bottom: -300px; | |
| opacity: 0.5; | |
| } | |
| .fire-glow-3 { | |
| width: 900px; | |
| height: 900px; | |
| left: 50%; | |
| bottom: -600px; | |
| transform: translateX(-50%); | |
| opacity: 0.7; | |
| } | |
| .fire-glow-4 { | |
| width: 1000px; | |
| height: 1000px; | |
| left: 10%; | |
| bottom: -600px; | |
| transform: translateX(-25%); | |
| opacity: 0.55; | |
| } | |
| .fire-glow-5 { | |
| width: 1100px; | |
| height: 1100px; | |
| right: 10%; | |
| bottom: -700px; | |
| transform: translateX(25%); | |
| opacity: 0.55; | |
| } | |
| .fire-glow-6 { | |
| width: 1200px; | |
| height: 1200px; | |
| bottom: -700px; | |
| left: -200px; | |
| opacity: 1; | |
| transform: translateX(-50%); | |
| opacity: 0.7; | |
| } | |
| .container { | |
| position: relative; | |
| width: inherit; | |
| height: inherit; | |
| } | |
| .inner-glow { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| border-radius: 100%; | |
| } | |
| .inner-glow-1 { | |
| height: 25%; | |
| width: 25%; | |
| background-color: #d86306; | |
| animation: flickerAnimation 4s ease-in-out infinite; | |
| z-index: 10; | |
| } | |
| .inner-glow-2 { | |
| height: 65%; | |
| width: 65%; | |
| background-color: #a62602; | |
| animation: flickerAnimation 3s ease-in-out infinite; | |
| z-index: 9; | |
| } | |
| .inner-glow-3 { | |
| height: 100%; | |
| width: 100%; | |
| background-color: #670101; | |
| animation: flickerAnimation 3.6s ease-in-out infinite; | |
| } | |
| @keyframes flickerAnimation { | |
| 0%, | |
| 100% { | |
| opacity: 0.9; | |
| scale: 0.98; | |
| } | |
| 15%, | |
| 80% { | |
| opacity: 1; | |
| scale: 1; | |
| } | |
| 30%, | |
| 60% { | |
| opacity: 0.8; | |
| scale: 1.05; | |
| } | |
| 45%, | |
| 75% { | |
| opacity: 0.95; | |
| scale: 0.97; | |
| } | |
| } | |
| /* ======== Sparks ======== */ | |
| .spark { | |
| position: absolute; | |
| border-radius: 100%; | |
| scale: 1; | |
| z-index: 999; | |
| } | |
| .spark .spark-glow { | |
| width: 200%; | |
| height: 200%; | |
| transform: translate(-25%, -25%); | |
| background-color: inherit; | |
| border-radius: 100%; | |
| filter: blur(8px) brightness(1.5); | |
| z-index: 99; | |
| } |