细微之处见真章☞头像点击放大的实现

最近在做淘宝逛逛的业务,在体验相关友商的产品后发现我们的个人主页头像居然没有点击放大的功能,产品说:我们也加一个点击放大吧。我想:一个简单的点击放大,那不是分分钟就搞完了。头像加个点击事件,再把隐藏的图层放出来,over。

于是噼里啪啦敲了1个小时代码实现了,效果如下:

细微之处见真章☞头像点击放大的实现 第1张

呃。体验了一下发现貌似和友商的差距有点大。友商的效果如下:

细微之处见真章☞头像点击放大的实现 第2张

嗯。经过录屏后逐帧对比细看一下对方有几个细节点处理的比较好:

ex:另外一个App实现的时候细节处理就不到位--缩放回去后圆角和直角的变化比较突兀

细微之处见真章☞头像点击放大的实现 第3张

那么我们应该怎么才能实现一个比较流畅的效果呢?

一番思考,简单画个demo图如下:

细微之处见真章☞头像点击放大的实现 第4张

那么我们用到的API有:

需要处理的细节有: * 1. 放在iPhone点击穿透 * 2.给图片设置背景色,避免png图片的透明边角 * 3.动画开始后需要让原始的图片元素透明度为0,避免对原有的dom&业务有侵入影响 * 4.头像还原后移除相关dom节点避免不必要的性能损耗

具体代码实现如下:

import './avatarAnimation.less';
let bgPopupEle = null;
let avatarEleClone = null;
let avatarTarget = null;
let tempRealImg = null;//真实的展示元素--清晰大图
let tempImgStatus = 0;
let eleAnimationState = false; // 保存当前动画状态
function initPopupEle() {
    bgPopupEle = document.createElement('div');
    bgPopupEle.className = 'avatar-popup-wrap';
    bgPopupEle.style.cssText = 'display:block;z-index:1000';
    document.body.appendChild(bgPopupEle);
    bgPopupEle.addEventListener('click', function (event) {
        eleAnimationState = 'end';
        bgPopupEle.classList.add('avatar-popup-wrap-animation-end');
        avatarEleClone.classList.add('avatar-clone-animation-end');
        bgPopupEle.removeChild(tempRealImg);
        event.stopPropagation();
        event.preventDefault();
        return false;
    });
    // 添加touchmove事件,避免事件穿透
    bgPopupEle.addEventListener('touchmove', function (event) {
        event.stopPropagation();
        event.preventDefault();
        return false;
    });
    bgPopupEle.addEventListener('transitionend', function (event) {
        if (
            eleAnimationState === 'end' &&
            event.propertyName === 'background-color'
        ) {
            avatarTarget.style.opacity = 1;
            eleAnimationState = false;
            document.body.removeChild(bgPopupEle);
            bgPopupEle = null;
            avatarEleClone = null;
            return true;
        }
        if (
            eleAnimationState === 'start' &&
            event.propertyName === 'background-color'
        ) {
            avatarEleClone.style.backgroundColor += `rgba(248, 248, 248, 1)`;
            tempImgStatus++;
            if (tempImgStatus === 2) {
                // 高清大图加载完成&动画完成后显示大图
                tempRealImg.style.opacity = 1;
            }
        }
    });
}
function initAvatarCloneEle(avatarEle, rect, imgSrc) {
    avatarTarget = avatarEle;
    tempImgStatus = 0;
    avatarEleClone = avatarEle.cloneNode(false);//复制目标节点
    avatarEleClone.classList.add('avatar-clone');
    let cssText = `position:fixed;z-index:12;top:${rect.top}px;left:${rect.left}px;`;
    avatarEleClone.style.cssText = cssText;
    bgPopupEle.appendChild(avatarEleClone);
    setTimeout(() => {
        const option = getTransformNum(rect, tempRealImg);
        avatarEleClone.style.cssText += `;transform: translate(${option.x}px,${option.y}px) scale(${option.scale}); border-radius:0;`;
        bgPopupEle.classList.add('avatar-popup-wrap-animation');
        avatarEle.style.opacity = 0;
    }, 50);
    tempRealImg = new Image();
    tempRealImg.className = 'temp-avatar-img';
    tempRealImg.onerror = function () {
        tempRealImg.classList.add('temp-avatar-img-none');
    };
    tempRealImg.addEventListener('load', function () {
        if (tempRealImg.naturalWidth === 1) {
            // 加载异常
            tempRealImg.classList.add('temp-avatar-img-none');
        }
        tempImgStatus++;
        if (tempImgStatus === 2) {
            // 高清大图加载完成&动画完成后显示大图
            tempRealImg.style.opacity = 1;
        }
    });
    tempRealImg.src = imgSrc + '_790x10000.jpg';
    bgPopupEle.appendChild(tempRealImg);
}
function getTransformNum(rect, imgTemp) {
    // 计算位移信息,以及缩放的比例
        const target = imgTemp.getBoundingClientRect();
        const result = {};
        result.scale = target.width / rect.width;
        result.x = target.width / 2 - rect.left - rect.width / 2;
        result.y = target.top + target.height / 2 - rect.top - rect.height / 2;
        return result;
}
export function avatarAnimationStart(imgSrc) {
    if (eleAnimationState) {
        return true;
    }
    const avatarEle = document.getElementById('avatarImg');
     const  rect = avatarEle.getBoundingClientRect();
    initPopupEle();
    initAvatarCloneEle(avatarEle, rect, imgSrc);
    eleAnimationState = 'start';
}

相关css如下

@time: 0.3s;
.avatar-popup-wrap {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1000;
    width: 100vw;
    height: 100vh;
    background: rgba(0, 0, 0, 0);
    display: flex;
    justify-content: center;
    align-items: center;
    opacity: 1;
    transition: background-color @time ease-in-out;
    display: none;
}
.avatar-popup-wrap-animation {
    background: rgba(0, 0, 0, 1);
}
.avatar-popup-wrap-animation-end {
    background: rgba(0, 0, 0, 0) !important ;
}

.avatar-clone {
    width: 20.8vw;
    z-index: 12;
    height: 20.8vw;
    border-radius: 10.4vw;
    transform: translate(0) scale(1);
    transform-origin: center center;
    transition: border-radius 0.2s ease-in-out,
        background-color @time ease-in-out,
        transform @time cubic-bezier(0.6, 0.8, 0.36, 1);
    object-fit: cover;
}
.avatar-clone-animation-end {
    border-radius: 10.4vw !important;
    transform: translate(0) scale(1) !important;
}
.temp-avatar-img {
    position: fixed;
    top: 50vh;
    left: 0;
    z-index: 20;
    width: 100vw;
    height: 100vw;
    transform-origin: center center;
    transform: translate(-0, -50%);
    background-color: rgba(248, 248, 248, 1);
    opacity: 0;
    object-fit: cover;
}
.temp-avatar-img-none {
    display: none !important;
}

至此。一个点击方法的效果就完成了。录屏如下

细微之处见真章☞头像点击放大的实现 第5张


本文标题:细微之处见真章☞头像点击放大的实现
本文链接:https://56way.com/p/136.html
作者授权:除特别说明外,本文由 无路 原创编译并授权 小无路 刊载发布。
版权声明:本文不使用任何协议授权,您可以任何形式自由转载或使用。

发表评论

必填

选填

选填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。