首页 > 解决方案 > 使用动画属性模拟对象拟合

问题描述

我正在尝试制作一个需要从缩略图开始到全屏图像的动画的灯箱。

在缩略图状态下,图像将保存在具有固定纵横比(例如 3:2)的容器内。图库上的每张图片都必须适合这个分配的空间,覆盖它的整个表面(通常是object-fit: cover)。

在全屏状态下,容器将扩展为 100vw:100vh,并且图像可以扩展(甚至收缩,对于一些异常高的图像的情况)以适应 ( object-fit: contain)。

必须始终保持其<img>纵横比不变(在两种状态下以及在动画期间)。

我可以为容器的widthand设置动画height(它们具有绝对值)。

但是object-fit动画是离散的。

这是一个同时编码状态和容器动画的笔:https ://codepen.io/rslemos/pen/PoNMymR (要查看动画,应该从元素中删除expanded类)。container

是否有任何 CSS 技巧可以object-fit使用线性属性进行模拟?

编辑

在动画过程中,图像本身——我的意思是,被位图覆盖的表面,而不是<img>元素,它可能比<img>任何方向都大或小——应该从它的初始大小变形到最终大小。

我还编辑了笔以显示容器纵横比(宽度:高度)< 1 时的初始状态(使用相同的图像,纵横比 > 1)。

标签: htmlcss

解决方案


解决方案看起来很复杂但不是很复杂,它只是一堆数学......

  1. 首先,您需要弄清楚图像的比例与您想要将其放入的框(div 或整个屏幕)的比例之间的差异。

  2. 然后,您需要根据比率差异(参见步骤 1)以及是否要覆盖(最大化最小尺寸)或包含(最小化最大尺寸)来确定尺寸是否受高度或宽度的约束

  3. 最后,您需要在不同的框(div 或整个屏幕)之间切换。这里我没有考虑 div 的位置(总是左上角),因为我希望这个演示的数学保持简单。

const img = document.querySelector('img')
const div = document.querySelector('.box')

// initialize some stuff
const {offsetHeight: boxHeight, offsetWidth: boxWidth} = div
let naturalHeight, naturalWidth
img.addEventListener('load', () => {
  naturalHeight = img.naturalHeight
  naturalWidth = img.naturalWidth  
  scaleToBox()
  requestAnimationFrame(
    () => img.classList.remove('initial')
  )
})

function scaleToBox() {
  let scale
  let x = 0, y = 0
  // is image ratio wider than container ratio
  if (boxWidth / boxHeight < naturalWidth / naturalHeight) {
    // width is the largest dimension, maximize height to cover
    scale = boxHeight / naturalHeight
    // center along x axis
    x = -(naturalWidth * scale - boxWidth) / 2 / scale
  } else {
    // height is the largest dimension, maximize width to cover
    scale = boxWidth / naturalWidth
    // center along y axis
    y = -(naturalHeight * scale - boxHeight) / 2 / scale
  }
  img.style.setProperty('transform', `scale(${scale}) translate(${x}px, ${y}px)`)
}

function scaleToScreen() {
  let scale
  let x = 0, y = 0
  // is image ratio wider than container ratio
  if (innerWidth / innerHeight < naturalWidth / naturalHeight) {
    // width is the largest dimension, maximize width to contain
    scale = innerWidth / naturalWidth
    // center along y axis
    y = -(naturalHeight * scale - innerHeight) / 2 / scale
  } else {
    // height is the largest dimension, maximize height to contain
    scale = innerHeight / naturalHeight
    // center along x axis
    x = -(naturalWidth * scale - innerWidth) / 2 / scale
  }
  img.style.setProperty('transform', `scale(${scale}) translate(${x}px, ${y}px)`)
}


let state
div.addEventListener('click', () => {
  if (!naturalHeight || !naturalWidth) {
    return
  }
  
  div.classList.toggle('full')  
  if (!state) {
    scaleToScreen()
  } else {
    scaleToBox()
  }

  state = !state
})
body {
  margin: 0;
}

.box {
  position: relative;
  height: 200px;
  width: 300px;
  transition: clip-path 1s;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
}

.box.full {
  clip-path: polygon(0 0, 100vw 0, 100vw 100vh, 0 100vh);
}

img {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  transition: transform 1s;
}

img.initial {
  height: 100%;
  width: 100%;
  object-fit: cover;
  transition-duration: 0s;
}
<div class='box'>
  <img class='initial' src="https://picsum.photos/1000/400"/>
</div>

即使父 div 不完全位于左上角,您也可以通过动画转换图像来改进此演示(我不想混淆此演示的工作原理)。


推荐阅读