<div id="map">
  <div id="pegman-container">
    <div id="pegman"> </div>
  </div>
</div>

<!-- pre render images -->
<img src="https://maps.gstatic.com/tactile/pegman_v3/santa/runway-2x.png" height="0" width="0" /> 
<img src="https://maps.gstatic.com/tactile/pegman_v3/santa/dropping-2x.png" height="0" width="0" /> 
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --map-image: url('https://res.cloudinary.com/rodrigoantunes/image/upload/v1734020140/santa_fh4gfz.png');
  --pegman-initial: url('https://maps.gstatic.com/tactile/pegman_v3/santa/runway-2x.png');
  --pegman-moving: url('https://maps.gstatic.com/tactile/pegman_v3/santa/dropping-2x.png');
}

body {
  overflow: hidden
}

#map {
  width: 100%;
  height: 100dvh;
  background-color: bisque;
  background-image: var(--map-image);
  background-size: cover;
  background-position: center center;
  position: relative;
  display: flex;
  justify-content: flex-end;
  align-items: flex-end;
  padding: 15px;
}

#pegman-container {
  position: absolute;
  background-color: #fff;
  height: 70px;
  aspect-ratio: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 4px;
  box-shadow: 2px 2px 4px rgba(0,0,0,0.3)
}

#pegman {
  width: 35px;
  height: 55px;
  background-image: var(--pegman-initial);
  background-size: 55px;
  background-position: -10px -65px;
  rotate: var(--r);
  transform-origin: 50% 0%;
  transition: rotate 200ms;
  cursor: grab;
}

#pegman:active {
  background-size: 100px;
  background-image: var(--pegman-moving);
  cursor: grabbing;
  background-position: -25px -35px;
}
const pegman = document.querySelector('#pegman');
let isDragging = false;
let initialX = 0;
let initialY = 0;
let inactivityTimeout;
let lastX = 0;
const timeout = 25;
const maxDegrees = 50;

const onMouseDown = (e) => {
  isDragging = true;
  initialX = e.clientX;
  initialY = e.clientY;
};

const onMouseMove = (e) => {
  if (!isDragging) return;

  const dy = e.clientY - initialY;
  const dx = e.clientX - initialX;

  let rx = Math.max(-maxDegrees, Math.min(maxDegrees, dx - lastX));
  pegman.setAttribute('style', `--r: ${rx}deg`);

  pegman.animate({ translate: `${dx}px ${dy}px` }, {
    duration: 100,
    fill: 'forwards',
  });

  clearTimeout(inactivityTimeout);

  inactivityTimeout = setTimeout(() => {
    lastX = dx;
    pegman.setAttribute('style', `--r: ${0}deg`);
  }, timeout);
};

const onMouseUp = () => {
  isDragging = false;

  pegman.setAttribute('style', `--r: ${0}`);
  pegman.animate({ translate: `0px 0px` }, {
    duration: 500,
    fill: 'forwards',
    easing: 'ease',
  });

  inactivityTimeout = setTimeout(() => {
    lastX = 0;
  }, timeout);
};

pegman.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.