Text-shadow Film Effect

Text-shadow Film Effect

In this post, we’re going to cover a stylish cursor and text effect that I made for my portfolio site.

If you hover your mouse over the words, you’ll see what I’m talking about.

If you’re unable to interact with the jsfiddle, here is a .gif to demonstrate:

We’re going to code this text-shadow effect. To me, this is what a film projector looks like when it’s not working right and it’s a bit out of focus. I put this into my portfolio site because the effect reminds me of how technology can sometimes can be a little spooky.

If you’re viewing from a mobile phone, sorry, this tutorial requires a mouse!

Languages

  • HTML
  • CSS
  • Javascript (vanilla)

Topics

  • Event listeners
  • CSS text-shadow
  • X/Y coordinates and animation

Code

All of this code can be found in a JS Fiddle, but here it is too.

HTML:

(Just 2 divs)

<div class="center">
  <div class="hover-text">
    Hover over me!
  </div>
</div>



CSS:

(Some rules to center it)

.center {
  height: 300px;
  width: 400px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: white;
  color: black;
  font-size: 32px;
  line-height: 200%;
  text-align: center;
}

.hover-text { cursor: crosshair; }



Javascript:

(Magic!)

let hoverText = document.querySelector('.hover-text')
hoverText.addEventListener('mouseover', startShadow)
hoverText.addEventListener('mousemove', moveShadow)
hoverText.addEventListener('mouseout', hideShadow)


let HoverData = {
  blur: 0,
  shadowX: 0,
  shadowY: 0,
  target: null,
  interval: null
}

function startShadow(e) {
  HoverData.target = e.target
  clearInterval(HoverData.interval)
  HoverData.interval = setInterval(animateBlur, 60)
}

function moveShadow(e) {
  let shadowX = -(e.clientX - (e.target.getBoundingClientRect().left + (e.target.getBoundingClientRect().width / 2)))
  let shadowY = -(e.clientY - (e.target.getBoundingClientRect().top + (e.target.getBoundingClientRect().height / 2)))
  HoverData.shadowX = shadowX
  HoverData.shadowY = shadowY
  HoverData.target = e.target

}

function animateBlur() {
  let focusChance = Math.floor(Math.random() * 50)
  let minimumBlurSize = 4
  let blurRange = 2
  HoverData.blur = focusChance ? ((Math.random() * blurRange) + minimumBlurSize) : 1
  HoverData.target.style.textShadow = `${HoverData.shadowX * 2.5}px ${HoverData.shadowY * 2.5}px ${HoverData.blur}px black`
  HoverData.target.style.display = 'block'
}

function hideShadow(e) {
  e.target.style.textShadow = ''
  clearInterval(HoverData.interval)
  HoverData.target = null
  HoverData.interval = null
}



Pseuodocode

So what exactly is going on in the Javascript? I always find it helps to do some pseudocoding to help me understand and plan what I’m about to do.

//  First, add Event Listeners to the .hover-text HTML element

function startShadow(e) {
  // Show the shadow and start the animation
}

function moveShadow(e) {
  // Track the mouse
  // Update the X/Y coordinates of the text-shadow based on the mouse coordinates
}

function animateBlur() {
  // Animate the "blur" on the text-shadow to give it that film effect
}

function hideShadow(e) {
  // Hide the shadow and stop the animations
}

Event Listeners are used to start and stop the shadow, and track the user’s mouse.

We’re going to be using 3 event listeners to achieve this: ‘mouseover’, ‘mouseout’ and ‘mousemove’,.

To demonstrate what they do, start by moving your mouse over this red box.

  • When your mouse enters the box, the box turns blue. This is the ‘mouseover’ event.
  • When your mouse leaves the box, the box turns red again. This is the ‘mouseout’ event.
  • While your mouse moves around inside the box, the X/Y coordinates of your mouse appear in the box and update every time your mouse moves. This is the ‘mousemove’ event.

If your’re unable to interact with the jsfiddle, here’s a .gif:

Text shadows need an X value, Y value, blur amount, and a color.

You’ve probably seen text shadows on really bad slideshow presentations.

Text-shadow is a CSS property that you can apply to any element with text inside of it. You give it an X coordinate amount, a Y coordinate amount, a size for the blurriness, and a color for the shadow.

Remember that X/Y coordinates start on the top-left and go towards the bottom right. So:

  • Positive(+) X values make the shadow go to the right.
  • Negative(-) X values make the shadow go left.
  • Positive(+) Y values make the shadow go down.
  • Negative(-) Y values make the shadow move up.
** offset-x | offset-y | blur-radius | color **
text-shadow: 25px 25px 2px red;

Click “Edit in JS Fiddle” if you’d like to play around with text-shadows a little bit.

Keeping the data organized.

So, if we need to keep track of an X value, a Y value, the blurriness amount, and a couple of other things, what’s the best way to do this?

let HoverData = {
  blur: 0,
  shadowX: 0,
  shadowY: 0,
  target: null,
  interval: null
}

I’ve decided to use a javascript object to do this, but there are a lot of ways to keep your code organized and what you choose really depends on what you (and your team when you have one) decide is the most appropriate.

For example, you can save all your numbers in globally scoped javascript variables.

let x = 0;
let y = 0;
let blur = 0;
let target = null;
let interval = null;

This might be OK for small projects, but you would benefit from not getting into a habit of this. In larger projects, you can have collisions where you accidentally define 2 global variables with the same name.

If you use an object, it’s sort of like organizing your variables into their own special drawers. If you have a lot of variables describing similar things (maybe like catX and dogX), you can further specify which x or y you’re talking about with an object’s namespace. If you have an object named Cat, then Cat.x will be namespaced to Cat and will not collide with Dog.x or even a variable just named x.

So this is why I use an object and get to call things like HoverData.x and HoverData.y.

Setting the text-shadow’s X/Y coordinates involves some math.

When the mouse moves over the text, we need to calculate the specific X/Y coordinates of the mouse relative to the center of the text.

Just a brief disclaimer. I hate math. When I do math, my heart rate increases, I get anxious, and I always have a flashback to sitting in some sort of wooden desk.

To make this calculation, we need to grab several measurements from the DOM.

For the Box’s height/width and coordinates, we’re going to use getBoundClientRect(), which is a function you can call on any HTML element to obtain its measurements. It’s very useful!

To get the mouse’s X/Y coordinates on the page, we’re going to use clientX and clientY which come with vanilla javascript.

I can’t very effectively explain how the formulas work. Hopefully, the JS fiddle I provide here will allow you to intuitively grasp how the values we calculate from the mouse movements get put into the CSS text-shadow attribute.

The Formulas:

function moveShadow(e) {
  let shadowX = -(e.clientX - (e.target.getBoundingClientRect().left + (e.target.getBoundingClientRect().width / 2)))
  let shadowY = -(e.clientY - (e.target.getBoundingClientRect().top + (e.target.getBoundingClientRect().height / 2)))
}

Formulas Demo:

Animating the blur with a setInterval.

Finally, we’re going to cover how to animate the blurriness to make the shadow look like it’s being projected by an out-of-focus film projector.

Before we begin, let’s pseudocode what’s happening here.

Code:
function startShadow(e) {
  //shortened version
  HoverData.interval = setInterval(animateBlur, 60)
}

function hideShadow(e) {
  //shortened version
  clearInterval(HoverData.interval)
}

function animateBlur() {
  let focusChance = Math.floor(Math.random() * 50)
  let minimumBlurSize = 4
  let blurRange = 2
  HoverData.blur = focusChance ? ((Math.random() * blurRange) + minimumBlurSize) : 1
  HoverData.target.style.textShadow = `${HoverData.shadowX * 2.5}px ${HoverData.shadowY * 2.5}px ${HoverData.blur}px black`
  HoverData.target.style.display = 'block'
}
Pseudocode:
function startShadow(e) {
//  When your mouse hovers over the text, start the animation
//  at a speed of 60 milliseconds
}

function hideShadow(e) {
//  When your mouse leaves the text, stop the animation
}


function animateBlur() {
// **This function will be called every 60 milliseconds**

//  let focusChance = a random number from 0 - 49
//  let minimumBlurSize = 4 pixels
//  let blurRange = 2 pixels
//
//  If focusChance is any number between 1 and 49,
//  choose a random pixel amount between 4 and 6.
//  Otherwise, if focusChance is 0 (which is the equivalent of 'false'),
//  Set the blur amount to 1px (which makes it look focused).
//
//  Continuously update the text with a
//  text-shadow with the Blur amount that is
//  calculated from here.
}

The animation depends on a lot of random number generation. Every 60ms (the speed of the animation), a new blur amount is rolled between 4-6px. At the same time, a second random number is rolled between 0-49. If it rolls a 0, the blur amount will be 1px which will bring the text into sharp focus for a single instant. To increase the chances of this happening, reduce the range to something like 0-10.

Just like with the coordinates of the text shadow, we’re going to save the blur amount into the HoverData object and then pull that data into the text-shadow attribute. Instead of pulling the data every time the mouse moves, however, the data is updated every 60 milliseconds from the animation.

HoverData Demo:

So there’s my explanation for how this effect works. I tried to cover as much as possible but if I missed anything please feel free to ask about it the comments.

Thanks for reading!


Categories:
coding   portfolio


Because every coding blog needs a comments section.

Please keep comments respectful! Harassment and general arrogance will not be tolerated.