今天偶然发现了百度网盘页面的登录框处,当鼠标在背景图上mousemove的时候,背景图会很神奇的跟着鼠标的移动而移动和放大!,页面地址:百度网盘

于是准备自己也模仿一下实现:

首先是利用devTool去查看一下大致的效果,发现百度是在:

<div id="login-container">
    <div class="ibg-bg index-banner-0" style="transform: matrix(1, 0, 0, 1, 0, 0); transition: all 500ms linear 0s;"></div>
</div>

login-container里面有一个div元素,然后加上了transform: matrix(1, 0, 0, 1, 0, 0); transition: all 500ms linear 0s;的效果,当mousemove的时候,其中的样式会变化成:transform: matrix(1.05, 0, 0, 1.05, xxx, xxx);

Matrix矩阵

具体的解释:理解CSS3 transform中的Matrix(矩阵),MDN上的解释不如张鑫旭大佬的通俗易懂...

matrix表现偏移就是:

transform: matrix(与我无关, 哪位, 怎么不去高考, 打麻将去吧, 水平偏移距离, 垂直偏移距离);

你只要关心后面两个参数就可以了,至于前面4个参数,是牛是马,是男是女都没有关系的

矩阵计算的同色标注

一图以蔽之...

也就是当表现平移的时候,最后两个xxx属性才是关键,这也是为什么百度网盘里面当鼠标移动的时候只会主要变动最后两个的原因,但是前面还有1.05呢?这个可以从上面的图示中看出来:

matrix(s, 0, 0, s, 0, 0) =>

x' = ax+cy+e = s*x+0*y+0 = s*x; y' = bx+dy+f = 0*x+s*y+0 = s*y; =>

也就是:matrix(sx, 0, 0, sy, 0, 0);,等同于scale(sx, sy)

所以1.05也就是放大1.05倍了

模拟实现

直接贴出目前的自己模拟出来的最终版了:

<div class="wrapper">
    <div id="container" class="container" style="transform: matrix(1, 0, 0, 1, 0, 0);"></div>
</div>
.container {
    width: 100%;
    height: 100%;
    transition: all 500ms linear 0s;
    background: url('./img/bg3.jpg') no-repeat center center;
}
.wrapper {
    position: relative;
    width: 960px;
    height: 550px;
    margin: 50px auto 0;
    overflow: hidden;
}
var container = document.getElementById("container");
container.addEventListener('mousemove', throttle(handleMouseMove));
container.addEventListener('mouseleave', throttle(handleMouseLeave));

function handleMouseMove(e) {
    // 鼠标往上,则图片往下,则(a,b,c,d,e,f)中,f的需要增加,向下则相反
    // 鼠标往右,则图片往左,则(a,b,c,d,e,f)中,e的需要减少,向左则相反
    var diffX = container.offsetWidth / 2 - (e.clientX - container.getBoundingClientRect().left);
    var diffY = container.offsetHeight / 2 - (e.clientY - container.getBoundingClientRect().top);
    diffX = isLarge('e', diffX/10, 1.05);
    diffY = isLarge('f', diffY/10, 1.05);
    setMatriex(1.05, diffX, diffY);
}
// 判断是否是超出平移的边界
function isLarge(type, val, scale) {
    const isPositive = val > 0;
    // 边界条件
    switch (type) {
        case 'e': {
            const halfWidth = container.offsetWidth / 2 * (scale - 1);
            if (Math.abs(val) >= halfWidth) {
                return isPositive ? halfWidth : -halfWidth;
            } else {
                return val;
            }
        }
        case 'f': {
            const halfHeight = container.offsetHeight / 2 * (scale - 1);
            if (Math.abs(Math.abs(val) - halfHeight) < 15) {
                return isPositive ? halfHeight : -halfHeight;
            } else {
                return val;
            }
        }
        default:
            break;
    }
}
function handleMouseLeave(e) {
    setMatriex(1, 0, 0);
}
function setMatriex(scale,e,f) {
    const styleStr = `matrix(${scale}, 0, 0, ${scale}, ${e}, ${f})`;
    container.style.transform = styleStr;
}
// 原文:https://blog.csdn.net/qq_27626333/article/details/81458824
function throttle(fn, delay = 300, atleast = 300) {
    let timer = null
    let previous = null
    return (...args) => {
        const now = +new Date()
        if (!previous) previous = now
        if (atleast && now - previous > atleast) {
            fn.apply(this, args)
            previous = now
            clearTimeout(timer)
        } else {
            clearTimeout(timer)
            timer = setTimeout(() => {
                fn.apply(this, args)
                previous = null
            }, delay)
        }
    }
}

思路和遇到的坑

说一下自己的思路和遇到的坑:

一开始直接用devTools去看,然后大致发现有变化时以图片的中心来变化的,其次利用devTools里面的eventListener确定的是logo-container上面监听了mouseLeavemousemove函数(原来还有mouseleave...),而且eventListener神器还有一个定位源码的功能!!,于是在百度的压缩js中找到了大致的处理逻辑(用了jq,但自己没用所以只能参考一下),源码找出来如下:

// 其中的g,k什么的没找到,但是大致思路还是很好的看出来了
l.mousemove( function (p) { 
  if (!l.hasClass("ibg-entering") && !l.hasClass("exiting")) {
    var n = p.pageX || p.clientX;
    var h = p.pageY || p.clientY;
    var n = (n - l.offset().left) - (g / 2);
    var h = (h - l.offset().top) - (k / 2);
    var q = ((f * n)) * -1;
    var o = ((j * h)) * -1;
    l.find("> .ibg-bg")
      .css({
        "transform": "matrix(" + m.scale + ",0,0," + m.scale + "," + q + "," + o + ")",
        transition: "all " + m.animationSpeed + " linear" })
    } 
})
.mouseleave(function (h) {
  if (m.scale !== 1) {
    l.addClass("ibg-exiting")
  }
  l.addClass("ibg-exiting")
    .find("> .ibg-bg")
    .css({
      transform: "matrix(1,0,0,1,0,0)",
      transition: "all " + m.animationSpeed + " linear" })
    .on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd",
      function () {
        l.removeClass("ibg-exiting") 
      })
})

然后自己实现时发现的一些坑有:

  1. 知道为什么百度的那个需要一个container了...
  2. 使用offsetLeft似乎getBoundingClientRect().left精确..
  3. 一开始很蠢的使用了debounce..
  4. 鼠标移动到靠边的时候,发现背景图出现移出...

接下来一个个解释一下;

container的必要性

一开始直接的用一个元素+Backgroun-Image的方式,实际一测就知道了,1.05的放大就直接让图片溢出边框了..

所以一定得要使用一个wrapper+hidden的模式

offetSetLeft和getBoundingClientRect().left

怎么计算鼠标离当前中心点的距离呢?

我的思路是:

水平方向:

从事件e中取出clientX,然后获取到container到边界的left值(这里是使用offsetLeft还是getBoundingClientRect().left呢?遇到一个比较诡异的是一开始出现offsetLeft不等于getBoundingClientRect().left,但是写到现在又发现一致...),获取这两个值之后,计算(e.clientX - container.getBoundingClientRect().left)的差值,然后再用水平方向宽度的一半container.offsetWidth / 2减去刚刚的差值,就可以了

垂直方向:

同理

throttle而不是debounce

debounce确保的是连续触发,停止后,只触发最后那次

throttle确保的是连续触发,在某个wait时间内会至少触发一次

背景图移出

第一反应只要鼠标移到靠边界的时候,把移动的值设置固定的container的一半宽度不就好了??

想着很好,但是却想错了...

保证不溢出的,实际可移动的范围其实只有,(1.05 * containerWidth - containerWidth ) / 2,也就是 containerWidth * 0.05 / 2

标签: none

添加新评论