<div class="stage">
  <i class="target" id="raw"></i>
  <span class="label">Raw value</span>
</div>
<div class="stage">
  <i class="target" id="interpolated"></i>
  <span class="label">Interpolated value</span>
</div>
<div class="stage">
  <i class="target" id="interpolated-tight"></i>
  <span class="label">Strongly interpolated value</span>
</div>
body {
  background-color: rgb(2, 62, 80);
  display: flex;
  min-height: 100vh;
  align-items: center;
  justify-content: center;
  font-family: monospace;
  color: #fff;
  font-size: 14px;
}

.stage {
  width: 30vw;
  height: 30vw;
  border: 1px solid #fff;
  margin: 1vw;
  position: relative;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M0 0v10h10' stroke='rgba(255,255,255,0.15)' fill='none'/%3E%3C/svg%3E");
}

.target {
  width: 5%;
  height: 5%;
  background: #fff;
  position: absolute;
  top: 0;
  left: 0;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  transition-property: top, left;
  transition-duration: 100ms;
  transition-timing-function: ease-out;
}

.label {
  position: absolute;
  bottom: calc(100% + 0.5vw);
  left: 0;
  right: 0;
  text-align: center;
}
interface Coordinates {
  lat: number;
  lng: number;
};


const interpolateCoordinates = (interpolation: number = 0.5): {
  get: () => Coordinates | null;
  set: (newValue: Coordinates) => Coordinates;
} => {
  // Initial states
  let lastValue: Coordinates | null = null;
  let currentValue: Coordinates | null = null;
  
  /**
   * Gets the interpolated coordinates between the last and current values
   */
  const get = (): Coordinates | null => {
    if (!lastValue || !currentValue) return null;
    
    // Calculate difference in stored values
    const latDiff = (currentValue.lat - lastValue.lat);
    const lngDiff = (currentValue.lng - lastValue.lng);
    
    // Calculate interpolated values
    return {
      lat: lastValue.lat + (latDiff * interpolation),
      lng: lastValue.lng + (lngDiff * interpolation),
    };
  };
  
  /**
   * Stores the current interpolation as the last value and updates the current value
   */
  const set = (newValue: Coordinates): Coordinates => {
    // Update last value with current interpolated value if available
    const interpolatedValue = get() ?? newValue;
    lastValue = {...interpolatedValue};
    
    // Update current value
    currentValue = {...newValue};
    
    // Return new interpolated value
    return get();
  };
  
  
  return {
    get,
    set,
  };
};


// Demo
const coordinates = interpolateCoordinates(0.1);
const coordinatesTight = interpolateCoordinates(0.01);

const targetRaw = document.getElementById('raw');
const targetInterpolated = document.getElementById('interpolated');
const targetInterpolatedTight = document.getElementById('interpolated-tight');

const loop = () => {
  const clock = Date.now();
  
  // Generate coordinates
  const newCoordinates: Coordinates = {
    lat: 50 + (40 * Math.sin(clock * 0.001)),
    lng: 50 + (40 * Math.cos(clock * 0.001)),
  };
  
  // Add interference
  const interference = 20;
  newCoordinates.lat += ((interference * -0.5) + (Math.random() * interference));
  newCoordinates.lng += ((interference * -0.5) + (Math.random() * interference));
  
  // Use raw new coordinates
  targetRaw.style.top = `${newCoordinates.lat}%`;
  targetRaw.style.left = `${newCoordinates.lng}%`;
  
  // Interpolate coordinates
  const interpolated = coordinates.set(newCoordinates);
  targetInterpolated.style.top = `${interpolated.lat}%`;
  targetInterpolated.style.left = `${interpolated.lng}%`;
  
  // Strongly interpolated coordinates
  const interpolatedTight = coordinatesTight.set(newCoordinates);
  targetInterpolatedTight.style.top = `${interpolatedTight.lat}%`;
  targetInterpolatedTight.style.left = `${interpolatedTight.lng}%`;
};

setInterval(loop, 100);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.