Skip to content

Latest commit

 

History

History
198 lines (130 loc) · 6.43 KB

detail.md

File metadata and controls

198 lines (130 loc) · 6.43 KB

fly-image的基本原理为:

  1. 在页面上创建图片查看区,该区域的大小占满整个页面;
  2. 创建目标图,图片地址与原图相同;
  3. 计算原图位于页面的位置;
  4. 将目标图预置于原图的位置;
  5. 开始飞行,让目标图移动到屏幕合适的位置,展示合适的大小。

以下详细解释每个步骤的具体实现方法。

查看区的创建

查看区其实就是在document.body内放置一个绝对定位的元素,其核心样式为:

.container {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
}

请注意,上面所述查看区的大小占满整个页面其实指的是占满整个屏幕,考虑到页面具有滚动条的情况,其大小有可能大于屏幕。因此,上述的页面指的其实都是屏幕。

如何计算原图位于页面的位置?

目前使用 Element.getBoundingClientRect() API进行实现,因为该API可以非常轻松地获取到元素的大小和位于视口(正是我们想要的)的位置。

考虑到兼容性的问题,后续会对该操作进行降级处理。

目标图预置于原图的位置

得到原图位于视口的位置和尺寸后,我们就可以提前将目标图放置在原图同样的位置,由于查看区的层级较高,所以实际上目标图位于原图的正上方。只要稍后让目标图”飞行“起来,看起来就像原图被放大了一样(为了更逼真,还可以让目标图飞行后,将原图暂时隐藏)。

由于目标图默认摆放在查看区的左上角,所以其在视口的坐标为(0, 0)。假设原图的大小为(400, 300),位于视口的位置为(0, 500),为目标图增加以下样式:

width: 400px;
transform: translate(0, 500px);

就可以让目标图刚好位于原图的位置。

如何处理飞行动画?

考虑到性能问题,使用CSS3的动效是好的方案。为目标图增加样式:

transition: transform .5s;

然后再添加飞行过渡,如:

transform: translate(500px, 100px);

目标图就会呈现从(0, 500)飞行到(500, 100)的动画。

需要特别注意的是,为目标图设置前后过渡样式的代码需要有一定的时间间隔,因为js代码的执行时间远大于DOM渲染的时间,如果使用这样的代码:

target.style.transform = 'translate(0, 500px)'
target.style.transform = 'translate(500px, 100px)'

则只有最终样式会被应用,自然也就看不到动画效果。比较好的做法是在DOM渲染的下一帧设置最终过渡样式:

function requestAnimationFrame(cb) {
  if (window.requestAnimationFrame) {
    window.requestAnimationFrame(cb)
  } else {
    setTimeout(() => {
      cb()
    }, 16)
  }
}

target.style.transform = 'translate(0, 500px)'
requestAnimationFrame(() => {
  target.style.transform = 'translate(500px, 100px)'
})

为了解决兼容性问题,当浏览器不支持window.requestAnimationFrame时,使用一个简短的定时来模拟。

目标图飞行的最终大小和位置

目标图飞行的最终大小和位置其实就是用户最终看到的图片大小,首先可以明确的一点是:

目标图的最终展示位置位于查看区的中间。

但是,目标图最终展示为多大,这个分多种情况,接下来,我们将由易到难一一进行分析。

1. 目标图的展示宽度固定

假设页面中有一张图片,位于视口的偏左下角:

图中cwch为视口宽高,w0h0为图片宽高。将这张图放大查看的效果如下图所示:

由于目标图的宽度是固定的,因此,这种模式的核心在于:

制定目标图的展示宽度规则。

例如,我们可以将目标图按照视口宽度的90%进行展示,因此最终的目标图展示宽度为:

w1 = cw * 0.9

为了让目标图显示在查看区的正中间,需要分成两步进行:

  1. 将目标图移动查看区的正中间:
// x轴飞行距离
x = (cw - w0) / 2
// y轴飞行距离
y = (ch - h0) / 2
  1. 将目标图缩放到最终的展示大小:
// 缩放倍数
scale = w1 / w0

这种做法非常简单,但是存在一个明显的缺陷:

如果原图非常小,将目标图展示到视口宽度的90%的大小,图片会被放大很多倍,导致不清晰。

2. 动态计算目标图的展示宽度

为了解决固定展示大小引起的失真问题,我们可以灵活设置目标图的最终展示宽度:

  • 当原图的实际宽度(不是原图在页面上的宽度,而是原图的实际像素宽度)小于查看区的宽度时,目标图的最终展示宽度为实际宽度;
  • 否则,使用方法1,按照宽度计算规则计算目标图的最终展示宽度。

伪代码如下:

if (aw > cw) { // aw为实际宽度
  w1 = cw * 0.9
} else {
  w1 = aw
}
// 缩放倍数
scale = w1 / w0

这个方案其实很容易理解,当原图的实际宽度很大甚至超过了查看区的宽度时,我们即可认为这张图至少在查看区的整个区域内进行展示,都是不会失真的,因此,只要不继续放大图片,就不会影响展示图的清晰度。

而当原图实际宽度小于查看区宽度时,由于按照固定宽度可能会导致图片展示失真,所以不如干脆不进行任何缩放处理,直接按照原图的实际尺寸进行展示。

再次提醒,原图位于页面的尺寸并不一定是原图的实际尺寸,判断是否缩放是根据实际尺寸,而不是页面尺寸。

在这种模式下,不同尺寸的图片可能有两种展示方式:

这种方法仍然有缺陷:

虽然原图的尺寸较小,但是就是要将其放大一定倍数,该怎么处理?

流程如下:

3. 由用户决定目标图的展示宽度

为了解决方法2的缺陷,我们为用户提供一个选项,用户可以自行决定目标图的展示宽度占查看区的百分比。

假设展示的宽度占比为size = 0.5,那么最终计算展示宽度的伪代码如下:

if (size > 0) {
  w1 = cw * size
} else {
  if (aw > cw) {
    w1 = cw * 0.9
  } else {
    w1 = aw
  }
}

这样,最终我们就确定了目标图的最终展示尺寸。