<div id="mouse-container">
    <div id="lottie"></div>
</div>
<div id="cursor_preloader"></div>
:root {
  --chame-color: rgba(158, 231, 152, 1);
  --chame-color-eyes: rgba(158,231,152,1) ;
}

body,
html {
  background-color: rgba(255, 255, 255, 1);
  margin: 0px;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
}

#lottie {
  background-color: rgba(20, 20, 20, 1);
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0px;
  top: 0px;
  display: block;
  overflow: hidden;
  transform: translate3d(0, 0, 0);
  box-sizing: border-box;
}

#mouse-container {
  width: 100%;
  height: 100%;
}

#mouse-container.active {
  cursor: url(https://labs.nearpod.com/bodymovin/demo/chameleon/cursor_empty_32.png), url(https://labs.nearpod.com/bodymovin/demo/chameleon/cursor_empty_32.cur), auto;
}

#cursor_preloader {
    cursor: url(https://labs.nearpod.com/bodymovin/demo/chameleon/cursor_empty_32.png), url(https://labs.nearpod.com/bodymovin/demo/chameleon/cursor_empty_32.cur), auto;
  position: absolute;
}

.chameleon_color {
  stroke: var(--chame-color);
  fill: var(--chame-color);
  transition: stroke 2s, fill 2s;
}

.chameleon_color_eyes {
  stroke: var(--chame-color-eyes);
  fill: var(--chame-color-eyes);
  transition: stroke 2s, fill 2s;
}

.mac_hidden .mac_arrow {
  display: none!important;
}

.default_hidden .default_arrow {
  display: none!important;
}
var chamecolor = "";
var degToRads = Math.PI / 180;
var valueArr = [100, 500, 100];
var mouse_changed = false;
var minTongueRadius = 405,
  maxTongueRadius = 415;
var angle = 0,
  distanceToMouse = 0,
  isActive = false;
var camouflage_timeout = null;
var colorSelector = document.querySelector(":root");
var mouse_container = document.getElementById("mouse-container");
var animationContainer = document.getElementById("lottie");
var isMacLike = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)?true:false;

animationContainer.setAttribute('class',isMacLike ? 'default_hidden' : 'mac_hidden')
var animData = {
  container: animationContainer,
  renderer: "svg",
  loop: true,
  autoplay: true,
  rendererSettings: {
    preserveAspectRatio: "xMidYMid meet"
  },
  path: "https://labs.nearpod.com/bodymovin/demo/chameleon/chameleon2.json"
};
anim = lottie.loadAnimation(animData);
var animationAPI;

var leftEyeCircles = [
  {
    name: "Group 12",
    radius: 27,
    divisor: 20
  },
  {
    name: "Group 13",
    radius: 27,
    divisor: 20
  },
  {
    name: "Group 14",
    radius: 27,
    divisor: 20
  },
  {
    name: "Group 15",
    radius: 23,
    divisor: 20
  },
  {
    name: "Group 16",
    radius: 21,
    divisor: 35
  },
  {
    name: "Group 17",
    radius: 19,
    divisor: 50
  },
  {
    name: "Group 18",
    radius: 17,
    divisor: 65
  },
  {
    name: "Group 19",
    radius: 15,
    divisor: 80
  },
  {
    name: "Group 20",
    radius: 13,
    divisor: 95
  },
  {
    name: "Group 21",
    radius: 5,
    divisor: 75
  }
];
var rightEyeCircles = [
  {
    name: "Group 1",
    radius: 27,
    divisor: 20
  },
  {
    name: "Group 2",
    radius: 27,
    divisor: 20
  },
  {
    name: "Group 3",
    radius: 27,
    divisor: 20
  },
  {
    name: "Group 4",
    radius: 23,
    divisor: 20
  },
  {
    name: "Group 5",
    radius: 21,
    divisor: 35
  },
  {
    name: "Group 6",
    radius: 19,
    divisor: 50
  },
  {
    name: "Group 7",
    radius: 17,
    divisor: 65
  },
  {
    name: "Group 8",
    radius: 15,
    divisor: 80
  },
  {
    name: "Group 9",
    radius: 13,
    divisor: 95
  },
  {
    name: "Group 10",
    radius: 5,
    divisor: 75
  }
];

var leaves = ["leaf_1", "leaf_2", "leaf_3", "leaf_4"];

anim.addEventListener("DOMLoaded", function() {
  //anim.setSubframe(false);
  camouflage_timeout = setTimeout(function() {
            colorSelector.style.setProperty('--chame-color', 'rgba(240,237,231,1)');
            colorSelector.style.setProperty('--chame-color-eyes', 'rgba(228,225,218,1)');
            camouflage_timeout = null;
        }, 1000);

  animationAPI = lottie_api.createAnimationApi(anim);

  window.addEventListener("mousemove", updateValue);
  window.addEventListener("touchmove", updateValue);
  window.addEventListener("resize", onWindowResized);

  addMouthProperties();
  addTongueProperties();
  addArrowProperties();
  addEyeCircles();
  addLeavesListeners();
});

function addEyeCircleProperty(circleData, eye, cachedMouseEyeData) {
  //
  var eyeContainer = animationAPI.getKeyPath(
    eye + ",Contents," + circleData.name + ",Transform,Position"
  );
  var lastValue = null,
    eye_angle;
  animationAPI.addValueCallback(eyeContainer, function(currentValue) {
    if (!lastValue) {
      lastValue = [currentValue[0], currentValue[1]];
    }
    if (!isActive) {
      var trasformedPoint = animationAPI.toContainerPoint(valueArr);
      trasformedPoint = animationAPI.toKeypathLayerPoint(
        eyeContainer,
        trasformedPoint
      );
      cachedMouseEyeData.distance = Math.sqrt(
        Math.pow(trasformedPoint[0], 2) + Math.pow(trasformedPoint[1], 2)
      );
      cachedMouseEyeData.eye_angle =
        Math.atan2(0 - trasformedPoint[1], 0 - trasformedPoint[0]) / degToRads +
        179;
      cachedMouseEyeData.current[0] = valueArr[0];
      cachedMouseEyeData.current[1] = valueArr[1];
    }
    eye_angle = cachedMouseEyeData.eye_angle;
    var distance = cachedMouseEyeData.distance;
    distance = distance > circleData.radius ? circleData.radius : distance;
    var newValueX =
      currentValue[0] + distance * Math.cos(eye_angle * degToRads);
    var newValueY =
      currentValue[1] + distance * Math.sin(eye_angle * degToRads);
    lastValue[0] =
      lastValue[0] + (newValueX - lastValue[0]) / circleData.divisor * 3;
    lastValue[1] =
      lastValue[1] + (newValueY - lastValue[1]) / circleData.divisor * 3;
    //return currentValue;
    return lastValue;
  });
}

function addEyeCircles() {
  var i,
    len = leftEyeCircles.length;
  // len = 1;
  var cachedMouseEyeData = {
    current: [-1, -1],
    distance: 0,
    eye_angle: 0
  };
  for (i = 0; i < len; i += 1) {
    addEyeCircleProperty(
      leftEyeCircles[i],
      "Loop,left_eye",
      cachedMouseEyeData
    );
  }
  len = rightEyeCircles.length;
  cachedMouseEyeData = {
    current: [-1, -1],
    distance: 0,
    eye_angle: 0
  };
  // len = 1;
  for (i = 0; i < len; i += 1) {
    addEyeCircleProperty(
      rightEyeCircles[i],
      "Loop,right_eye",
      cachedMouseEyeData
    );
  }
}

function changeColor(leave_name) {
  var leafColorKey = animationAPI.getKeyPath(
    "#" + leave_name + ",Contents,color_group,fill_prop,Color"
  );
  var colorValue = leafColorKey.getPropertyAtIndex(0).getValue();
  var colorString = "rgba(";
  colorString += Math.round(colorValue[0]);
  colorString += ",";
  colorString += Math.round(colorValue[1]);
  colorString += ",";
  colorString += Math.round(colorValue[2]);
  colorString += ",1)";
  colorSelector.style.setProperty("--chame-color", colorString);
  colorSelector.style.setProperty("--chame-color-eyes", colorString);
  if (camouflage_timeout) {
    clearTimeout(camouflage_timeout);
  }
  camouflage_timeout = setTimeout(function() {
    colorSelector.style.setProperty("--chame-color", "rgba(240,237,231,1)");
    colorSelector.style.setProperty("--chame-color-eyes", "rgba(228,225,218,1)");
    camouflage_timeout = null;
  }, 15000);
}

function addLeaveListener(leave_name) {
  var leaveElement = document.getElementById(leave_name);
  leaveElement.addEventListener("mouseover", function() {
    changeColor(leave_name);
  });
  leaveElement.addEventListener("touchmove", function() {
    changeColor(leave_name);
  });
}

function addLeavesListeners() {
  var i,
    len = leaves.length;
  for (i = 0; i < len; i += 1) {
    addLeaveListener(leaves[i]);
  }
}

function addMouthProperties() {
  var keyPathMouthInner = animationAPI.getKeyPath("Mouth,ReferencePoint");
  var keyPathMouthContainerTimeRemap = animationAPI.getKeyPath(
    "Mouth,Time Remap"
  );
  var perc = 0;
  animationAPI.addValueCallback(keyPathMouthContainerTimeRemap, function(
    currentValue
  ) {
    if (!isActive && mouse_changed) {
      var point2 = animationAPI.toContainerPoint(valueArr);
      point2 = animationAPI.toKeypathLayerPoint(keyPathMouthInner, point2);
      angle = Math.atan2(0 - point2[1], 0 - point2[0]) / degToRads + 170;
      distanceToMouse = Math.sqrt(
        Math.pow(0 - point2[0], 2) + Math.pow(0 - point2[1], 2)
      );
      mouse_changed = false;
    }

    if (distanceToMouse < minTongueRadius) {
      perc = distanceToMouse / minTongueRadius;
      return perc * 9 / 30;
    } else if (distanceToMouse > maxTongueRadius) {
      perc =
        1 -
        Math.min(
          1,
          (distanceToMouse - maxTongueRadius) / (maxTongueRadius + 100)
        );
      return perc * (9 / 30);
    } else if (distanceToMouse >= minTongueRadius) {
      return 9 / 30;
    }
    return 0;
  });
}

function addArrowProperties() {
  var scalePath = "Mouth,Tongue_Comp,arrow,Contents,Shape 1,Transform,Scale";
  var rotationPath = "Mouth,Tongue_Comp,arrow,Contents,Shape 1,Transform,Rotation";
  var scaleKeyPath, rotationKeyPath;
  if(isMacLike) {
    scaleKeyPath = "Mouth,Tongue_Comp,.mac_arrow,Contents,Shape 1,Transform,Scale";
    rotationKeyPath = "Mouth,Tongue_Comp,.mac_arrow,Contents,Shape 1,Transform,Rotation";
  } else {
    scaleKeyPath = "Mouth,Tongue_Comp,.default_arrow,Contents,Shape 1,Transform,Scale";
    rotationKeyPath = "Mouth,Tongue_Comp,.default_arrow,Contents,Shape 1,Transform,Rotation";
  }
  var keyPathArrowScale = animationAPI.getKeyPath(
    scaleKeyPath
  );
  var currentScale = -1;
  var currentScaleValue = [-1, -1];
  animationAPI.addValueCallback(keyPathArrowScale, function(currentValue) {
    var scale = animationAPI.getScaleData().scale;
    if (currentScale !== scale) {
      currentScaleValue[0] = currentValue[0] / scale;
      currentScaleValue[1] = currentValue[1] / scale;
      currentScale = scale;
    }
    return currentScaleValue;
  });

  var keyPathArrowRotation = animationAPI.getKeyPath(
    rotationKeyPath
  );
  animationAPI.addValueCallback(keyPathArrowRotation, function(currentValue) {
    return -angle;
  });
}

function addTongueProperties() {
  var tongueInitialAnimationTime = 0;
  var tongueCurrentTime = 0;

  function animateTongue() {
    tongueInitialAnimationTime = Date.now() - 1500 / 30;
    isActive = true;
    mouse_container.setAttribute("class", "active");
  }

  function resetTongue() {
    isActive = false;
    mouse_container.setAttribute("class", "");
  }
  var keyPathTongueContainerTimeRemap = animationAPI.getKeyPath(
    "Mouth,Tongue_Comp,Time Remap"
  );
  animationAPI.addValueCallback(keyPathTongueContainerTimeRemap, function(
    currentValue
  ) {
    if (
      distanceToMouse > minTongueRadius &&
      distanceToMouse < maxTongueRadius &&
      !isActive
    ) {
      animateTongue();
    }
    if (isActive) {
      tongueCurrentTime = 2 * (Date.now() - tongueInitialAnimationTime) / 1000;
    }
    if (tongueCurrentTime > 2) {
      tongueCurrentTime = 0;
      resetTongue();
    }
    return tongueCurrentTime;
  });

  var keyPathTongueContainer = animationAPI.getKeyPath(
    "Mouth,Tongue_Comp,Transform,Rotation"
  );
  animationAPI.addValueCallback(keyPathTongueContainer, function(currentValue) {
    return angle;
  });
}

function updateValue(ev) {
  mouse_changed = true;
  var mouseX, mouseY;
  if (ev.touches && ev.touches.length) {
    var mouseX = ev.touches[0].pageX;
    var mouseY = ev.touches[0].pageY;
  } else if (ev.pageX !== undefined) {
    mouseX = ev.pageX;
    mouseY = ev.pageY;
  }
  valueArr[0] = mouseX;
  valueArr[1] = mouseY;
}

function onWindowResized() {
  if (animationAPI) {
    anim.resize();
    animationAPI.recalculateSize();
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://labs.nearpod.com/bodymovin/demo/chameleon/lottie_light.min.js
  2. https://labs.nearpod.com/bodymovin/demo/chameleon/lottie_api.minified.js