5.4 Improving Mouse Parallax

Watch the video course


What you'll learn:

  • Implementing useTransform with MotionValue
  • Arrow functions
  • useSpring

useTransform to improve useMotionValue

Let’s add some improvements to our code here. Remember useTransform? We can use it to make our code a little bit tidier.

We’ll start by importing useTransform and creating a couple of MotionValues for our mouse position.

import "./styles.css"

function App() {
let mouseX = useMotionValue(0) let mouseY = useMotionValue(0) ...

For birdX, instead of creating a MotionValue from scratch, we’ll create one that will change along with mouseX.

function App() {
  let mouseX = useMotionValue(0)
  let mouseY = useMotionValue(0)

let birdX = useTransform(mouseX, [], [])
let birdY = useTransform(mouseY, [], [])
... }

For the following parameters, we need an input and output range such as the ranges we used in the Tinder Swipe example.

However, this time, let’s try something different. By looking at the useTransform documentation, we can see that there are only two parameters when calling useTransform in the example on the right. We know that there are only two parameters because there is only one comma.

Arrow Functions

transform image

The first parameter is the source MotionValue called parent in the documentation and the second parameter is something we'll learn about now!

;(value) => value * 2

What is this equal sign and greater-than symbol here?

It turns out that it’s just a function. That statement is equivalent to writing this.

function(value) {
  return value * 2;

In context,

let birdX = useTransform(mouseX, (value) => value * 2)


let birdX = useTransform(mouseX, function (value) {
  return value * 2

The only difference is that one is shorter and looks nicer. I hope you know which one that is!

How useTransform works with MotionValue

Let’s look at how this version of useTransform works.

let birdX = useTransform(mouseX, function (value) {
  return value * 2

First of all, useTransform will create a new MotionValue but not from scratch. It uses mouseX as a reference, takes out the value from mouseX, calls this anonymous function, and puts the return value into a new MotionValue. To illustrate, if mouseX is 100, birdX would be assigned a value of 200.

Furthermore, whenever the value of mouseX changes, the process I just listed would be repeated: the anonymous function is called with the new mouseX value and returns the new value into birdX.

In our case, we want the return value to be divided by 3.5 and not multiplied by 2.

let birdX = useTransform(mouseX, function (value) {
  return value / 3.5

Function => arrow function

As you’ve just seen, we can convert the function to a shorter form in a few steps.

  1. Removing function

    let birdX = useTransform(mouseX, (value) => {
      return value / 3.5

    Because of this "=>" sign that looks like a fat >arrow, these kinds of functions are called arrow >functions.

  2. Removing ()

    When there’s only one parameter, we can ignore the >parentheses.

    let birdX = useTransform(mouseX, (value) => {
      return value / 3.5
  3. Removing {} and the return statement

    When a function only has a return statement, we >can remove the keyword return and the curly brackets.

    let birdX = useTransform(mouseX, (value) => value / 3.5)

Making the same changes to our other variables,

let birdX = useTransform(mouseX, value => value / 3.5)
let birdY = useTransform(mouseY, value => value / 3.5)
let cloudsX = useTransform(mouseX, value => value / 8)
let cloudsY = useTransform(mouseY, value => value / 8)
let sunX = useTransform(mouseX, value => value / 10)
let sunY = useTransform(mouseY, value => value / 10)
let bgX = useTransform(mouseX, value => value / 14)
let bgY = useTransform(mouseY, value => value / 14)

MotionValue animation

Now, inside of onMouseMove, we can remove all our set functions and replace them with two lines!

  onMouseMove={function(event) {
  let offsetX = event.clientX - window.innerWidth / 2
  let offsetY = event.clientY - window.innerHeight / 2


However, there’s one more issue. The movement of all these image layers now feels a bit stiff.

stiff animation

It no longer has that bouncy effect as our previous solution with useAnimation.

bouncy animation

* Check it out yourself: Parallax Animation

This is because, unlike useAnimation, useMotionValue does not animate with a spring. However, the fix is really simple.


If we check out the Framer documentation again, we can find something called useSpring.

use spring documentation

Therefore, instead of importing and calling useMotionValue, we'll import and call useSpring.

import "./styles.css"

function App() {
let mouseX = useSpring(0) let mouseY = useSpring(0) ...
final result

We now have our spring animation back.


Now that we know how to use both useAnimation, useMotionValue and how to convert animation styles, we'll learn about the differences between the two in the next post while we improve our "Tinder" slider!.