Using GSAP to fade colour (or any var) of a material, on mouse over/out

Hi, Firstly I really tried to make a CodePen of this, but can’t get the imports for three.interactive working (duplicate THREE errors). So I’ll explain and hope it’s easy to follow. The latests dev version I’m referring to while writing is here:

I have a scene with some doors. When a user mouses-over those doors I want the colour to change slightly to show they are interactive. Here’s what I have so far:

  1. To detect the mouse I’m using three.interactive, and firing a function on ‘mouseover’ and ‘mouseout’ events.

  2. I have two materials setup, except one is emissive, with an intensity of ‘0’.

  3. I’m using GSAP to change the emissiveIntensity over a short duration, then back.

A problem can be seen on the dev site ( though. If I mouse over and out very fast and onto another door, multiple doors retain the wrong material. Please try it to see what I mean. Easiest way is to mouse across the whole line of doors.

What I have:


import gsap from 'gsap'
import * as THREE from 'three'
import { InteractionManager } from 'three.interactive'


const doorMaterial = new THREE.MeshPhongMaterial({
    color: 0x9b6d54

const doorHoverMaterial = new THREE.MeshPhongMaterial({
    color: 0x9b6d54,
    emissive: 0xffffff,
    emissiveIntensity: 0


// loader looping through a model assigning following for all door parts
child.addEventListener('mouseover', doorcolour)
child.addEventListener('mouseout', doorcolour)

The function:

function doorcolour(evt) {
    if ( != '') {
        doorid =
    } else {

    doormesh = model.getObjectById(parseInt(doorid))

    if (evt.type == 'mouseover') { = 'pointer'
        doormesh.material = doorHoverMaterial,{
            duration: 0.2

    } else if (evt.type == 'mouseout') {
        = 'default',{
            duration: 0.2,
            onComplete: function(){ 
                doormesh.material = doorMaterial

    } else {

Can anyone suggest I how should be waiting for animations to finish, or how maybe I can stop and reverse where an animation is. I’m guessing that sometimes the onComplete doesn’t fire on the mouseout. But I do have the event’s console.logged and it doesn’t look like there’s always a mouseout event for every interaction. Thank you for your time :slight_smile:

Here is an example that uses Tween to animate a THREE.Color from one to the next.
Example : Tweening THREE.Color

easy to make race conditions with animations.

also easy to prevent that using just plain old lerp. i would suggest you do not stack and stagger stuff, wait for something to finish etc, because that will send you into a gaping hell hole.

const c = new THREE.Color()
function frameloop() {
  door1.material.color.lerp(c.set(door1hovered ? "white" : "brown"), 0.1)
  window1.material.color.lerp(c.set(window1hovered ? "white" : "green"), 0.1)

the up of this approach is that your logic is in a central place as opposed to what you have above where everything wildly mutates from everywhere — which is something that has no solution, it’s imo broken code. even if you hunt race conditions for weeks, this will always cause you headaches.

And here’s a colour transition using lerp
Example : Lerping THREE.Color

It’s for scaling though, still see no problem with the using of a tweening lib: three.js - ThreeJS - How do I scale down intersected object on "mouseout" - Stack Overflow

Thank you very much, I didn’t know about the lerp method, and the demos were invaluable to understand how to use it. I was definitely overusing the tween library.

I still need to see if something similar can be done with opacity as well as colour, but my code is already ten times better thank you.

This changes opacity over time
Tweening Opacity