///|
fn max(a : Double, b : Double) -> Double {
if a > b {
a
} else {
b
}
}
///|
fn min_int(a : Int, b : Int) -> Int {
if a < b {
a
} else {
b
}
}
///|
/// Result of CLS calculation
pub(all) struct LayoutShift {
impact_fraction : Double // 衝撃率: affected viewport fraction
distance_fraction : Double // 距離率: movement relative to viewport
score : Double // impact_fraction × distance_fraction
}
///|
pub impl Show for LayoutShift with fn output(self, logger) {
logger.write_string("LayoutShift { impact: ")
self.impact_fraction.output(logger)
logger.write_string(", distance: ")
self.distance_fraction.output(logger)
logger.write_string(", score: ")
self.score.output(logger)
logger.write_string(" }")
}
///|
/// Calculate layout shift between two bounding rects
/// viewport: the viewport size for normalization
pub fn compute_element_shift(
before : @types.BoundingRect,
after : @types.BoundingRect,
viewport : @types.Size[Double],
) -> LayoutShift {
let viewport_area = viewport.width * viewport.height
// Impact fraction: union of before and after areas / viewport area
let union_rect = before.union(after)
let impact_fraction = union_rect.area() / viewport_area
// Distance fraction: max movement / max viewport dimension
let dx = (after.x - before.x).abs()
let dy = (after.y - before.y).abs()
let max_distance = max(dx, dy)
let max_viewport_dim = max(viewport.width, viewport.height)
let distance_fraction = max_distance / max_viewport_dim
let score = impact_fraction * distance_fraction
{ impact_fraction, distance_fraction, score }
}
///|
/// Calculate total CLS score for multiple elements
pub fn compute_total(
before : Array[@types.BoundingRect],
after : Array[@types.BoundingRect],
viewport : @types.Size[Double],
) -> Double {
let len = min_int(before.length(), after.length())
let mut total = 0.0
for i = 0; i < len; i = i + 1 {
let shift = compute_element_shift(before[i], after[i], viewport)
total = total + shift.score
}
total
}