This is a little snippet I’ve wanted to try for quite some time. I finally got around to recreating this iconic feature from Google Maps: the ability to drag and drop this little guy, known as Pegman, to switch to Street View.
TL;DR
Click and drag the little icon in the bottom-right corner over the map:
HTML
The structure is straightforward. I have:
- A
#map
to serve as the container (with a background-image applied). - A
#pegman-container
positioned in the bottom-right corner. - The
#pegman
element itself for the draggable character.
<div id="map">
<div id="pegman-container">
<div id="pegman"> </div>
</div>
</div>
CSS
Positioning everything is simple, as you can see in the source code above, but the most important part is the rotate
property. This is dynamically updated using JavaScript:
#pegman {
/* other styles */
rotate: var(--r);
}
The rotate
value changes based on user interaction, which we’ll dive into next.
Javascript
Everything here revolves around user interaction. JavaScript handles:
- Listening for mouse events.
- Updating Pegman’s position and rotation dynamically.
- Adding smooth animations for better UX.
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;
// Event listeners
pegman.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
Let’s break down each function:
onMouseDown
This starts the drag by recording the initial mouse position and toggling the isDragging
flag.
const onMouseDown = (e) => {
isDragging = true;
initialX = e.clientX;
initialY = e.clientY;
};
onMouseMove
This handles Pegman’s movement and rotation during the drag. It ensures:
- Rotation stays within a range (to avoid extreme angles like Superman flying).
- The
rotate
value updates dynamically using--r
.
const onMouseMove = (e) => {
if (!isDragging) return;
const dy = e.clientY - initialY;
const dx = e.clientX - initialX;
// Limit rotation range
let rx = Math.max(-maxDegrees, Math.min(maxDegrees, dx - lastX));
pegman.setAttribute('style', `--r: ${rx}deg`);
// Animate Pegman's position
pegman.animate({ translate: `${dx}px ${dy}px` }, {
duration: 100,
fill: 'forwards',
});
// Reset rotation after inactivity
clearTimeout(inactivityTimeout);
inactivityTimeout = setTimeout(() => {
lastX = dx;
pegman.setAttribute('style', `--r: 0deg`);
}, timeout);
};
onMouseUp
This resets Pegman’s state once the user stops dragging:
- Rotation resets to 0.
- Pegman smoothly returns to its original position.
const onMouseUp = () => {
isDragging = false;
// Reset rotation
pegman.setAttribute('style', `--r: 0`);
// Animate Pegman back to its original position
pegman.animate({ translate: `0px 0px` }, {
duration: 500,
fill: 'forwards',
easing: 'ease',
});
// Clear residual state
inactivityTimeout = setTimeout(() => {
lastX = 0;
}, timeout);
};
Final Thoughts
This snippet highlights how simple animations and interactivity can recreate an iconic user experience. Key takeaways:
- Using CSS custom properties (like
--r
) keeps styling dynamic and manageable. - Rotation limits ensure a polished, natural feel for user interactions.
- Timeouts and easing animations add realism to the motion.
Feel free to fork the CodePen, make changes and let me know what you think! 🚀🗺️
By the way...
I'm using the original sprites from google, which you can see here and make experiments:
Did you know? ✨ The Google Maps location I used is the real Santa Claus Village!
Fun fact: Santa’s Finnish name is Joulupukki, and he originally from Finland 🎅🏻🎄
Top comments (2)
I thought Santa Claus was from Türkiye.
Interesting, didn't know that! Thanks for the info. Looks like Santa’s got roots in both Saint Nicholas from Turkey and Joulupukki in Finland! 🎅✈️