关键帧动画效果

一、先上依赖包

基本的vue3项目配置,需要安装d3和animejs的包。

二、准备一个路径文件path.svg

<svg width="500px" height="300px" viewBox="0 0 500 300" xmlns="http://www.w3.org/2000/svg">
    <path d="M45.716904,269.5 C250,269.5 250,30.5 454.283096,30.5" id="Path-3"/>
</svg>

注意svg文件中的 xmlns="http://www.w3.org/2000/svg" 是必须存在的

三、动态加载svg

三、加载path路径给path设置样式,并且添加移动图片

 四、配置关键帧数据,处理关键帧数据

export const keyframesList = [
    {
        anim:[
            {
                style: 'opacity',
                from: "0",
                to: "50",
                start: "0",
                end: "30",
            },
            {
                style: 'opacity',
                from: "50",
                to: "100",
                start: "30",
                end: "40",
            },
            {
                style: 'opacity',
                from: "100",
                to: "0",
                start: "40",
                end: "80",
            },
            {
                style: 'opacity',
                from: "0",
                to: "100",
                start: "80",
                end: "100",
            },
            {
                style: 'translate',
                from: "0",
                to: "50",
                start: "0",
                end: "30",
            },
            {
                style: 'translate',
                from: "50",
                to: "100",
                start: "30",
                end: "40",
            },
            {
                style: 'translate',
                from: "100",
                to: "0",
                start: "40",
                end: "80",
            },
            {
                style: 'translate',
                from: "0",
                to: "100",
                start: "80",
                end: "100",
            }
        ]
    }
]
export function initData(obj){
    // eslint-disable-next-line no-useless-escape
    let reg = /[-\d\.]+/g;
    let numArrFrom = obj.from.match(reg);
    let numArrTo = obj.to.match(reg);
    if (!numArrFrom || !numArrTo || numArrFrom.length == 0 || numArrTo.length == 0 || numArrFrom.length != numArrTo.length) {
        console.warn('数据输入错误');
    }
    for (let i = 0; i < numArrFrom.length; i++) {
        numArrFrom[i] = parseFloat(numArrFrom[i]);
        numArrTo[i] = parseFloat(numArrTo[i]);
    }
    let strArr = obj.from.split(reg);
    if (numArrFrom.length <= 0) {
        return;
    }
    return {
        strArr: strArr,
        numArrFrom: numArrFrom,
        numArrTo: numArrTo,
        totalLength: strArr.length + numArrFrom.length,
        isEnd: false,
    }
}

五、渲染关键帧动画

keyframesAnime脚本
export function keyframesAnime({pathDom,keyframesList, duration = 5000}){
    const pathLength = pathDom.getTotalLength();
    let keyframes = [];
    let list = []
    const frameLength = 1;
    // 计算全部路线
    for(let i = 0; i<= pathLength; i++){
        const framePosition = frameLength * i;
        const framePoint = pathDom.getPointAtLength(framePosition);
        const angle = getRotationAtPoint(pathDom, framePosition)
        // let keyframe;
        let keyframe = {
            duration: duration / pathLength,
            easing: 'linear',
            translateY:`${framePoint.y}`,
            translateX:`${framePoint.x}`,
            rotate:`${angle}`,
        };
        keyframes.push(keyframe)
    }
    // 计算关键帧路线及透明度
    for(let i = 0; i < keyframesList[0].anim.length; i++){
        let target = keyframesList[0].anim[i]

        if((Number(target.end) > Number(target.start) ) && !target.initData.isEnd){
            let percent = (target.end - target.start) / 100
            if(percent >= 1){
                target.initData.isEnd = true
            }
            if(target.style === "translate"){
                let index = i - keyframesList[0].anim.length/2
                if(target.initData.numArrFrom[0] > target.initData.numArrTo[0]){
                    let num = ((keyframesList[0].anim[index].initData.numArrFrom[0] - keyframesList[0].anim[index].initData.numArrTo[0]) / 100) /(Math.round(keyframes.length * (target.initData.numArrFrom[0]/100)) - Math.round(keyframes.length * (target.initData.numArrTo[0]/100)))
                    let opacityNum = 0
                    for(let j = Math.round(keyframes.length * (target.initData.numArrFrom[0]/100))-1; j > Math.round(keyframes.length * (target.initData.numArrTo[0]/100)); j--){
                        list.push({
                            ...keyframes[j],
                            duration:(duration * percent )/ (Math.round(keyframes.length * (target.initData.numArrFrom[0]/100)) - Math.round(keyframes.length * (target.initData.numArrTo[0]/100))),
                            opacity: opacityNum
                        })
                        opacityNum += num
                    }
                }else{
                    let num = ((keyframesList[0].anim[index].initData.numArrTo[0] - keyframesList[0].anim[index].initData.numArrFrom[0]) / 100) /(Math.round(keyframes.length * (target.initData.numArrTo[0]/100)) - Math.round(keyframes.length * (target.initData.numArrFrom[0]/100)))
                    let opacityNum = 0
                    for(let j= Math.round(keyframes.length * (target.initData.numArrFrom[0]/100)); j< Math.round(keyframes.length * (target.initData.numArrTo[0]/100)); j++){
                        list.push({
                            ...keyframes[j],
                            duration:(duration * percent )/ (Math.round(keyframes.length * (target.initData.numArrTo[0]/100)) - Math.round(keyframes.length * (target.initData.numArrFrom[0]/100))),
                            opacity:opacityNum
                        })
                        opacityNum += num
                    }
                }
            }
        }
    }
    return list;
}
function getRotationAtPoint(pathEl, length) {
    const startPoint = pathEl.getPointAtLength(length - 0.01);
    const endPoint = pathEl.getPointAtLength(length + 0.01);
    const angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x) * 180 / Math.PI;
    return angle;
}

 git地址vue-keyframes: js关键帧动画

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐