fly-image
的基本原理为:
- 在页面上创建图片查看区,该区域的大小占满整个页面;
- 创建目标图,图片地址与原图相同;
- 计算原图位于页面的位置;
- 将目标图预置于原图的位置;
- 开始飞行,让目标图移动到屏幕合适的位置,展示合适的大小。
以下详细解释每个步骤的具体实现方法。
查看区其实就是在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
时,使用一个简短的定时来模拟。
目标图飞行的最终大小和位置其实就是用户最终看到的图片大小,首先可以明确的一点是:
目标图的最终展示位置位于查看区的中间。
但是,目标图最终展示为多大,这个分多种情况,接下来,我们将由易到难一一进行分析。
假设页面中有一张图片,位于视口的偏左下角:
图中cw
、ch
为视口宽高,w0
、h0
为图片宽高。将这张图放大查看的效果如下图所示:
由于目标图的宽度是固定的,因此,这种模式的核心在于:
制定目标图的展示宽度规则。
例如,我们可以将目标图按照视口宽度的90%进行展示,因此最终的目标图展示宽度为:
w1 = cw * 0.9
为了让目标图显示在查看区的正中间,需要分成两步进行:
- 将目标图移动查看区的正中间:
// x轴飞行距离
x = (cw - w0) / 2
// y轴飞行距离
y = (ch - h0) / 2
- 将目标图缩放到最终的展示大小:
// 缩放倍数
scale = w1 / w0
这种做法非常简单,但是存在一个明显的缺陷:
如果原图非常小,将目标图展示到视口宽度的90%的大小,图片会被放大很多倍,导致不清晰。
为了解决固定展示大小引起的失真问题,我们可以灵活设置目标图的最终展示宽度:
- 当原图的实际宽度(不是原图在页面上的宽度,而是原图的实际像素宽度)小于查看区的宽度时,目标图的最终展示宽度为实际宽度;
- 否则,使用方法1,按照宽度计算规则计算目标图的最终展示宽度。
伪代码如下:
if (aw > cw) { // aw为实际宽度
w1 = cw * 0.9
} else {
w1 = aw
}
// 缩放倍数
scale = w1 / w0
这个方案其实很容易理解,当原图的实际宽度很大甚至超过了查看区的宽度时,我们即可认为这张图至少在查看区的整个区域内进行展示,都是不会失真的,因此,只要不继续放大图片,就不会影响展示图的清晰度。
而当原图实际宽度小于查看区宽度时,由于按照固定宽度可能会导致图片展示失真,所以不如干脆不进行任何缩放处理,直接按照原图的实际尺寸进行展示。
再次提醒,原图位于页面的尺寸并不一定是原图的实际尺寸,判断是否缩放是根据实际尺寸,而不是页面尺寸。
在这种模式下,不同尺寸的图片可能有两种展示方式:
这种方法仍然有缺陷:
虽然原图的尺寸较小,但是就是要将其放大一定倍数,该怎么处理?
流程如下:
为了解决方法2的缺陷,我们为用户提供一个选项,用户可以自行决定目标图的展示宽度占查看区的百分比。
假设展示的宽度占比为size = 0.5
,那么最终计算展示宽度的伪代码如下:
if (size > 0) {
w1 = cw * size
} else {
if (aw > cw) {
w1 = cw * 0.9
} else {
w1 = aw
}
}
这样,最终我们就确定了目标图的最终展示尺寸。