Moving car indicator with jump

The following code adds a small car that drives along the forest line I added in my previous blog post. It follows the mouse on the horizontal axis. If clicked it drives autonomously off the grid and returns from the other side - forever trapped in the forest. If clicked while driving away, the car will perform a front flip. You can see how it works on my art page.

HTML:

JAVASCRIPT:

<script>
document.addEventListener('DOMContentLoaded', function () { 
const subString = '/art';
const getURL = window.location.href;  
if (getURL.includes(subString)) { 
//mobile check
const headerActions = document.querySelector('.header-actions');
let isTouch = false;
function checkHeader() {
const styles = window.getComputedStyle(headerActions);
isTouch = styles.getPropertyValue('display') !== 'flex';
}
checkHeader();
if (isTouch === false) {
const footer = document.getElementById('footer-sections');
const bottomSeparator = footer.querySelector('.horizontalrule-block');
const carMainContainer = document.createElement('div');
carMainContainer.id = "carMainContainer";
carMainContainer.classList.add('carMainContainer');  
const carContainer = document.createElement('div');
carContainer.id = "carContainer";
carContainer.classList.add('carContainer'); 
const forestCar = document.createElement('div');
forestCar.id = "forestCar";
forestCar.classList.add('forestCar'); 
const forestCarInnerContainer = document.createElement('div');
forestCarInnerContainer.id = "forestCarInnerContainer";
forestCarInnerContainer.classList.add('forestCarInnerContainer'); 
forestCarInnerContainer.appendChild(forestCar);
carContainer.appendChild(forestCarInnerContainer);
carMainContainer.appendChild(carContainer);
bottomSeparator.appendChild(carMainContainer);
//setup
let debounceTimeout;
let lastCarX = 0;
let autodrive = false;
let autodriveJump = false;
const maxSpeed = 100; // pixels per second
const debounceTime = 1 * 1000;
const carMinDuration = 5;
const carMaxDuration = 10; 
let zoomFactor = parseFloat(document.body.style.zoom) || 1;
const containerRect = carMainContainer.getBoundingClientRect();
const carWidth = forestCarInnerContainer.offsetWidth;
forestCarInnerContainer.style.transform = `translateX(${-carWidth}px)`;
// Move car based on the calculated mouse position
function moveCar(mouseX) {
const containerRect = carMainContainer.getBoundingClientRect();
const containerLeft = containerRect.left;
let carX = mouseX - containerLeft - carWidth / 2;
if (carX < 0) {
carX = 0;
} else if (carX > containerRect.width - carWidth) {
carX = containerRect.width - carWidth;
}
const distance = Math.abs(carX - lastCarX);
lastCarX = carX;
const duration = distance / maxSpeed; 
const cappedDuration = Math.min(Math.max(duration, carMinDuration), carMaxDuration);
forestCarInnerContainer.style.transition = `transform ${cappedDuration}s ease-out`;
forestCarInnerContainer.style.transform = `translateX(${carX}px)`;
}
//initiate movement on mouse move
document.addEventListener('mousemove', function (event) {
if (autodrive === false) {
const mouseX = event.clientX;
moveCar(mouseX);
} 
});
//drive off and return
forestCar.addEventListener('click', function () {
if (autodrive === false) {
autodrive = true;
zoomFactor = parseFloat(document.body.style.zoom) || 1;
const farRightX = (containerRect.width / zoomFactor) + carWidth;
forestCarInnerContainer.style.transition = `transform ${carMaxDuration}s ease-out`;
forestCarInnerContainer.style.transform = `translateX(${farRightX}px)`;
setTimeout(() => {
forestCarInnerContainer.style.transition = 'none';
forestCarInnerContainer.style.transform = `translateX(${-carWidth}px)`;
lastCarX = -carWidth;
autodrive = false;
}, (carMaxDuration + 1) * 1000);
} else { //jump and flip if click while autodrive
if (autodriveJump === false) {
autodriveJump = true;
forestCar.style.transition = 'all 1s linear';
forestCar.classList.add('forestCarJump');   
setTimeout(() => {
forestCar.classList.remove('forestCarJump'); 
forestCar.style.transition = 'all 0s linear';
forestCar.style.transform = `translateY(${0}px) rotate(0deg)`;
autodriveJump = false;
}, 1 * 700); //match the css animation speed
}
}
});
//exit
} 
}
});
</script>
CSS:

@keyframes carJump {
50% {transform: translateY(-30px) rotate(180deg);}
100% {transform: translateY(0px) rotate(360deg);}
}


//CAR IN FOREST//
#carMainContainer {
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  pointer-events: none;
  width: 75%;
  height: 200px;
  transform: translateX(-50%);
  align-items: center;
  bottom: 50%;
  left: 50%;
  padding: 0; 
}
#carContainer {
  position: absolute;
  pointer-events: none;
  width: 100%;
  height: 100%;
  top: 0;
  -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%);
  mask-image: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%);
}
#forestCarInnerContainer {
  position: absolute;
  pointer-events: none;
  width: 56px !important;
  height: 28px !important;
  transition: all 1s linear;
  bottom: 0;
  left: 0;
  transform-origin: center;
}
#forestCar {
  position: absolute;
  pointer-events: auto;
  cursor: pointer;
  width: 56px !important;
  height: 28px !important;
  background: url('https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/836bffeb-d10a-491a-815b-b1b0b215eaa0/beetleCarIcon1.png') no-repeat center center;
  background-size: contain;
  transition: all 5s ease;
  margin-bottom: 1px;
  z-index: 99999;
  bottom: 0;
  opacity: 1;
  left: 0;
  padding-bottom: 0px;
}
#forestCar.forestCarJump {
  animation: carJump 0.7s linear forwards; 
}
Previous
Previous

Animated Logo with CSS and SVG

Next
Next

Procedural dynamic trees driven by local wind speed