cursor trails on a blurred image
<div class="image"></div>
<script src="jquery.js"></script>
<script src="cursor-trails.js"></script>
<script>
$('.image').blurredImage({
image: 'image-normal.jpg',
blur: 'image-with-blur.jpg',
lifetime: 1000, // time the trail remains on screen
width: 1000, // image width
height: 1000, // image height
minimumLineWidth: 25, // trail minimum width
maximumLineWidth: 100, // trail maximum width
maximumSpeed: 50, // at which speed should the trail get thinner (lower = more thinner trails)
shadowBlur: 30 // blur of the trails edges
});
</script>
/* globals jQuery */
/**
* Based on https://codepen.io/chrisdoble/pen/WQLLVp
*/
(function ($) {
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame
$.fn.blurredImage = function (options) {
if (!this.length > 0) {
return false
}
// This is the easiest way to have default options.
let settings = $.extend({
image: '',
blur: '',
lifetime: 1000,
width: 1280,
height: 720,
minimumLineWidth: 25,
maximumLineWidth: 100,
maximumSpeed: 50,
shadowBlur: 30
}, options)
let points = []
let image = $('<img src="' + settings.image + '">').hide()
let imageCanvas = $('<canvas>')
let imageCanvasContext = imageCanvas[0].getContext('2d')
let lineCanvas = $('<canvas>')
let lineCanvasContext = lineCanvas[0].getContext('2d')
this.append(image)
this.append(imageCanvas)
this.css('background', 'url(\'' + settings.blur + '\') 0 0 / cover')
if (image[0].complete) {
start()
} else {
image[0].onload = start
}
/**
* Attaches event listeners and starts the effect.
*/
function start () {
image.width(settings.width)
image.height(settings.height)
$(this).parent('div').width(settings.width)
$(this).parent('div').height(settings.height)
$(this).parent('div')[0].addEventListener('mousemove', function (event) {
points.push({
time: Date.now(),
x: event.offsetX,
y: event.offsetY
})
})
window.addEventListener('resize', function () {
imageCanvas[0].width = $(this).parent('div').width()
imageCanvas[0].height = $(this).parent('div').height()
lineCanvas[0].width = $(this).parent('div').width()
lineCanvas[0].height = $(this).parent('div').height()
}.bind(this))
window.dispatchEvent(new window.Event('resize'))
tick()
}
/**
* The main loop, called at ~60hz.
*/
function tick () {
// Remove old points
points = points.filter(function (point) {
return (Date.now() - point.time) < settings.lifetime
})
drawLineCanvas()
drawImageCanvas()
window.requestAnimationFrame(tick)
}
/**
* Draws a line using the recorded cursor positions.
*
* This line is used to mask the original image.
*/
function drawLineCanvas () {
let minimumLineWidth = settings.minimumLineWidth
let maximumLineWidth = settings.maximumLineWidth
let lineWidthRange = maximumLineWidth - minimumLineWidth
let maximumSpeed = settings.maximumSpeed
lineCanvasContext.clearRect(0, 0, lineCanvas[0].width, lineCanvas[0].height)
lineCanvasContext.lineCap = 'round'
lineCanvasContext.shadowBlur = 30
lineCanvasContext.shadowColor = '#000'
for (let i = 1; i < points.length; i++) {
let point = points[i]
let previousPoint = points[i - 1]
// Change line width based on speed
let distance = getDistanceBetween(point, previousPoint)
let speed = Math.max(0, Math.min(maximumSpeed, distance))
let percentageLineWidth = (maximumSpeed - speed) / maximumSpeed
lineCanvasContext.lineWidth = minimumLineWidth + percentageLineWidth * lineWidthRange
// Fade points as they age
let age = Date.now() - point.time
let opacity = (settings.lifetime - age) / settings.lifetime
lineCanvasContext.strokeStyle = 'rgba(0, 0, 0, ' + opacity + ')'
lineCanvasContext.beginPath()
lineCanvasContext.moveTo(previousPoint.x, previousPoint.y)
lineCanvasContext.lineTo(point.x, point.y)
lineCanvasContext.stroke()
}
}
/**
* @param {{x: number, y: number}} a
* @param {{x: number, y: number}} b
* @return {number} The distance between points a and b
*/
function getDistanceBetween (a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
}
/**
* Draws the original image, masked by the line drawn in drawLineToCanvas.
*/
function drawImageCanvas () {
// Emulate background-size: cover
let width = imageCanvas[0].width
let height = imageCanvas[0].width / image[0].naturalWidth * image[0].naturalHeight
if (height < imageCanvas[0].height) {
width = imageCanvas[0].height / image[0].naturalHeight * image[0].naturalWidth
height = imageCanvas[0].height
}
imageCanvasContext.clearRect(0, 0, imageCanvas[0].width, imageCanvas[0].height)
imageCanvasContext.globalCompositeOperation = 'source-over'
imageCanvasContext.drawImage(image[0], 0, 0, width, height)
imageCanvasContext.globalCompositeOperation = 'destination-in'
imageCanvasContext.drawImage(lineCanvas[0], 0, 0)
}
}
}(jQuery))