🌸

有几个效果的代码我整理到一个html文件中了,可直接保存为HTML文件运行。其中关于数据需要自行修改(例如下落的文字、相册),代码中使用的插件我放在我网站目录下了,嫌慢可自己放入cdn中,其他需求自行修改,也可以找我一起研究🐸暂时整理这几个,后面有需要了我再继续。

另外有没有大佬知道,为什么我的代码块渲染不出来呀!!现在这个效果很丑
现在的效果

现在的效果

我想要的

我想要的

文字坠落效果

鼠标进入区域掉落文字下来

鼠标进入区域掉落文字下来

先分享一个插件BRM·IOGithub连接,很牛逼,用法太多了,可以自己去看一下的是使用范例。(纯英文对我不是很友好😥,对这个感兴趣的话网上有很多教程)

Matter是一个开源的JavaScript物理引擎库,可以帮助开发者实现基于物理引擎的动画和交互效果。Matter提供了丰富的API,各种物理参数可以轻松配置,例如重力、摩擦力、弹簧等。开发者可以使用Matter实现物理运动、碰撞检测、拖拽等效果,帮助开发者节省大量时间和精力。Matter适用于在Web浏览器中创建交互式的游戏、模拟和动画。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 引入matter插件,需要的话自行下载引入 -->
    <script src="https://yaoyuan.vip/js/Plugins/matter.js"></script>
    <style>
        body { margin: 0; padding: 0; background: transparent; font-family: 'fantasy','Inconsolata', monospace; width: 100vw; height: 100vh; overflow: hidden;}
        * { user-select: none; }
        .word { position: absolute; cursor: grab; font-size: 25px; color: black; }
        .word.highlighted { font-weight: bold; color: #69C1AE;}
        #abstract { color: #69C1AE; font-size: 30px; position: absolute; width: 100vw; height: 100vh; display: flex;align-items: center;justify-content: center;align-items: center;}
    </style>
</head>
<body>
    <!-- 默认展示,可通过类名调节样式 -->
    <div id="abstract">姚远,软件工程,前端开发(就这些?)</div>
    <!-- 会通过空格对字符进行分割 -->
    <div id="text" style="display: none;"> 🐋 🐬 🐳 🐟 我叫姚远,🐋 🐬 🐳 🐟 姚远, 姚远 , 。 🐋 🐬 🐳 🐟 * @ ~  🐋 🐬 🐳 🐟  00后 00年 计算机  , 🐋 🐬 🐳 🐟 软件工程专业, 西安 , 前端开发 , 目前工作内容就是, 每天摸鱼写博客 , 干啥都很菜 脾气也不好 工作很摆烂 懒滴 , 也没啥写的, 主要是想整一些活, 打发打发时间 罢liao</div>
</body>
<script>
    const textNode = document.querySelector("#text");
    const abstract = document.querySelector("#abstract");
    const splitWords = () => {
        const text = textNode.textContent;
        const newDomElements = text.split(" ").map((text) => {
            // 被分割的字符如果开头是这三个字,那么会高亮显示
            const highlighted = text.startsWith(`"前"`) || text.startsWith(`软`) || text.startsWith(`姚`);
            return `<span class="word ${ highlighted ? "highlighted" : null }">${ text }</span>`;
        });
        textNode.innerHTML = newDomElements.join("");
    };

    const renderCanvas = () => {
        const Engine = Matter.Engine;
        const Render = Matter.Render;
        const World = Matter.World;
        const Bodies = Matter.Bodies;
        const Runner = Matter.Runner;
        const params = { isStatic: true, render: { fillStyle: "transparent" }
        };
        const canvasSize = { width: window.innerWidth, height: window.innerHeight };
        const engine = Engine.create({});
        const render = Render.create({ element: document.body, engine: engine, options: { ...canvasSize, background: "transparent", wireframes: false } });
        const floor = Bodies.rectangle( canvasSize.width / 2, canvasSize.height, canvasSize.width, 50, params );
        const wall1 = Bodies.rectangle( 0, canvasSize.height / 2, 50, canvasSize.height, params );
        const wall2 = Bodies.rectangle( canvasSize.width, canvasSize.height / 2, 50, canvasSize.height, params );
        const top = Bodies.rectangle( canvasSize.width / 2, 0, canvasSize.width, 50, params );
        const wordElements = document.querySelectorAll(".word");
        const wordBodies = [...wordElements].map((elemRef) => {
            const width = elemRef.offsetWidth;
            const height = elemRef.offsetHeight;
            return {
                body: Matter.Bodies.rectangle(canvasSize.width / 2, 0, width, height, { render: { fillStyle: "transparent" } }),
                elem: elemRef,
                render() {
                    const { x, y } = this.body.position;
                    this.elem.style.top = `${y - 20}px`;
                    this.elem.style.left = `${x - width / 2}px`;
                    this.elem.style.transform = `rotate(${this.body.angle}rad)`;
                }
            };
        });
        const mouse = Matter.Mouse.create(document.body);
        const mouseConstraint = Matter.MouseConstraint.create(engine, { mouse, constraint: { stiffness: 0.2, render: { visible: false}}});

        mouse.element.removeEventListener("mousewheel", mouse.mousewheel);
        mouse.element.removeEventListener("DOMMouseScroll", mouse.mousewheel);
        World.add(engine.world, [ floor, ...wordBodies.map((box) => box.body), wall1, wall2, top, mouseConstraint ]);
        render.mouse = mouse;
        Runner.run(engine);
        Render.run(render);

        (function rerender() {
            wordBodies.forEach((element) => { element.render(); });
            Matter.Engine.update(engine);
            requestAnimationFrame(rerender);
        })();
    };
    abstract.addEventListener("mouseover", (event) => {
        // 鼠标移入区域隐藏默认、显示文字、切割字符、绘制画布
        abstract.style.display = 'none'
        textNode.style.display = 'block'
        splitWords();
        renderCanvas();
    });
</script>
</html>

鼠标滑过脚印效果

鼠标移动绘制脚印
  • 在页面中插入脚印元素
<svg id="footstep" viewBox="0 0 0 0" style="position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;pointer-events:none">
  <defs>
    <path id="feet-shape"
      d="M41.5,30.2C36,24.6,4.7,26.1,7.7,49.4c.8,5.9,4,10.2,8,19.9,3,7.2-.1,15.7,5.8,20.8S43,91.6,38.6,75.9c-1.8-6.5-7.6-9.3-8.9-14.1C26.1,47.9,51.7,40.6,41.5,30.2Z M41.7,7.6c-2.6-.3-5.2,2.8-5.6,7s1.3,7.8,3.9,8.1,5.2-2.9,5.6-7.1S44.4,7.8,41.7,7.6Z M28.8,21.9c2.2.2,4.1-2.1,4.5-5.1s-1.1-5.7-3.2-6-4.1,2.1-4.5,5.1S26.7,21.6,28.8,21.9Z M20.1,23.3c1.6.1,3.1-1.8,3.4-4.4s-.8-4.7-2.4-4.9-3,1.8-3.3,4.3S18.5,23.1,20.1,23.3Z M14.9,25.5c1.4-.2,2.4-1.9,2.1-3.9s-1.6-3.4-3-3.3-2.4,2-2.2,3.9S13.4,25.7,14.9,25.5Z M10.9,29.2c1-.1,1.7-1.4,1.5-2.8s-1.1-2.5-2.2-2.4-1.7,1.4-1.6,2.8S9.8,29.3,10.9,29.2Z"/>
    <symbol id="feet-left" viewBox="0 0 100 100">
      <rect x="0" y="0" width="100" height="100" fill="none"/>
      <use xlink:href="#feet-shape"/>
    </symbol>
    <symbol id="feet-right" viewBox="0 0 100 100">
      <rect x="0" y="0" width="100" height="100" fill="none"/>
      <g transform="scale(-1, 1) translate(-100, 0)">
          <use xlink:href="#feet-shape"/>
      </g>
    </symbol>
  </defs>
</svg>
  • 添加执行的Js代码
const svgEl = document.querySelector('#footstep');
let feetEls = [];
const pointer = {
    x: 0,
    y: 0,
    dx: 0,
    dy: 0,
    angle: 0,
    moving: false,
    justStopped: false,
}
const stepsNumber = 9;
const iconSize = 50;
const mouseRepel = 35;
const feetPositions = [];

updateLayout();
window.addEventListener('resize', updateLayout);
createFeet();

let stepsCnt = 0;
let accumDx = 0;
let accumDy = 0;
let accumDist = 0;
let introAnimationIsPlaying = false;
render();
introAnimation();

window.addEventListener("mousemove", (e) => {
    if (!introAnimationIsPlaying) {
        onPointerMove(e.clientX, e.clientY)
    }
});
window.addEventListener("touchmove", (e) => {
    if (!introAnimationIsPlaying) {
        onPointerMove(e.targetTouches[0].clientX, e.targetTouches[0].clientY)
    }
});

function updateLayout() {
    svgEl.setAttribute("viewBox", "0 0 " + window.innerWidth + " " + window.innerHeight)
}

function createFeet() {
    for (let i = 0; i < stepsNumber; i++) {
        const el = document.createElementNS("http://www.w3.org/2000/svg", 'use');
        el.setAttribute("href", i % 2 ? "#feet-left" : "#feet-right");
        el.setAttribute("x", "-" + (.5 * iconSize));
        el.setAttribute("y", "-" + (.5 * iconSize));
        el.setAttribute("width", "" + iconSize);
        el.setAttribute("height", "" + iconSize);
        svgEl.appendChild(el);

        feetPositions.push({x: 0, y: 0, angle: 0, age: 0})
        feetEls.push(el);

        gsap.set(el, {
            opacity: 0,
            transformOrigin: "center center",
        })
    }
}

function onPointerMove(x, y) {
    pointer.dx = x - pointer.x;
    pointer.dy = y - pointer.y;
    pointer.x = x;
    pointer.y = y;
    pointer.moving = true;

    accumDx += pointer.dx;
    accumDy += pointer.dy;
    pointer.angle = Math.atan2(pointer.dx, pointer.dy);
    accumDist = Math.sqrt(Math.pow(accumDx, 2) + Math.pow(accumDy, 2));

    if (accumDist > 70) {
        stepsCnt++;
        accumDx = 0;
        accumDy = 0;
        accumDist = 0;

        feetPositions.unshift({
            x: pointer.x,
            y: pointer.y,
            angle: (1 - pointer.angle / Math.PI) * 180,
            age: 1
        });
        feetPositions.length = stepsNumber;
        feetPositions[0].x -= Math.sin(pointer.angle) * mouseRepel;
        feetPositions[0].y -= Math.cos(pointer.angle) * mouseRepel;
        for (let fIdx = 1; fIdx < stepsNumber; fIdx++) {
            updateFootEl(feetEls[fIdx], fIdx, (fIdx % 2 === stepsCnt % 2));
        }
        gsap.set(feetEls[0], {
            opacity: 0
        })
    }
}

function render() {

    for (let fIdx = 1; fIdx < (stepsNumber); fIdx++) {
        feetPositions[fIdx].age -= (pointer.moving ? .05 : .1);
    }
    for (let fIdx = 2; fIdx < (stepsNumber); fIdx++) {
        gsap.set(feetEls[fIdx], {
            opacity: feetPositions[fIdx].age
        })
    }
    if (pointer.moving) {
        pointer.moving = false;
        pointer.justStopped = true;
    } else if (pointer.justStopped) {
        pointer.justStopped = false;

        // 当鼠标停止移动时执行一次
        updateFootEl(feetEls[0], 0, (0 === stepsCnt % 2));
        gsap.set(feetEls[0], {
            opacity: 1
        })

        updateFootEl(feetEls[1], 0, (1 === stepsCnt % 2), .1);
        gsap.set(feetEls[1], {
            delay: .1,
            opacity: 1
        })

        for (let fIdx = 2; fIdx < (stepsNumber); fIdx++) {
            updateFootEl(feetEls[fIdx], fIdx - 1, ((fIdx - 1) % 2 === stepsCnt % 2));
        }
    }

    requestAnimationFrame(render);
}

function updateFootEl(el, posIdx, isLeft, delay = 0) {
    gsap.set(el, {
        delay: delay,
        x: feetPositions[posIdx].x,
        y: feetPositions[posIdx].y,
        rotation: feetPositions[posIdx].angle,
        attr: {
            href: isLeft ? "#feet-left" : "#feet-right"
        },
    })
}

function introAnimation() {
    introAnimationIsPlaying = true;
    const mouseCoords = {x: -100, y: window.innerHeight}
    gsap.timeline({
        onUpdate: () => {
            onPointerMove(mouseCoords.x, mouseCoords.y);
        },
        onComplete: () => {
            introAnimationIsPlaying = false;
        }
    })
        .to(mouseCoords, {
            x: .4 * window.innerWidth,
            ease: "power1.out"
        })
        .to(mouseCoords, {
            y: .6 * window.innerHeight,
            ease: "back.out(3)"
        }, 0);
}

跟屁虫小狗

小狗看向鼠标方向,点击后跟随
  • 在页面中插入小狗元素
<div class="wrapper">
  <!-- 小狗的位置 -->
  <div class="marker red d-none"></div>
  <!-- 鼠标的位置 -->
  <div class="marker green d-none"></div>
  <!-- 狗狗面朝位置-->
  <div class="marker blue d-none"></div>
  <div class="dog">
    <div class="body-wrapper">
      <div class="body img-bg"></div>
    </div>
    <div class="head-wrapper">
      <div class="head img-bg"></div>
    </div>
    <div class="leg-wrapper">
      <div class="leg one img-bg"></div>
    </div>
    <div class="leg-wrapper">
      <div class="leg two img-bg"></div>
    </div>
    <div class="leg-wrapper">
      <div class="leg three img-bg"></div>
    </div>
    <div class="leg-wrapper">
      <div class="leg four img-bg"></div>
    </div>
    <div class="tail-wrapper">
      <div class="tail img-bg"></div>
    </div>
  </div>  
</div>
  • 小狗样式
//修狗
.wrapper {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    // justify-content: center;
    // align-items: center;
    z-index: -1;
    // pointer-events:;
}
.wrapper .leg {
    position: absolute;
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAMCAYAAABfnvydAAAAAXNSR0IArs4c6QAAAElJREFUKFNjZICC/////4exQTQjIyMjmAYR6JIwhSBFjLMiLP6nLj+OrBnOnh1pyTBSFIA8jS0sQGGQtuIEJDhhipBDCyQJ4gMALug8VaRjkWwAAAAASUVORK5CYII=);
    width: calc(2 * 8px);
    height: calc(2 * 12px);
    background-size: calc(2 * 8px) calc(2 * 12px) !important; 
    transition: 0.15s;
}
.wrapper .body {
    position: absolute;
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAwCAYAAACxIqevAAAAAXNSR0IArs4c6QAABSpJREFUeF7tm32a1CAMxqe30pvoAfRQegC9id5qfPBZlGELJOQL2nf/2t0pkPySvA20czzwAwIgAAJBBI6gdbEsCIAACDwgQEgCEACBMAIQoDD0WBgEQAAChBwAARB4fPv04UnF8PXnbzXdUJuIajyuAwEQWINAKTpffvwiG/X988d/10rFCAJExo4LQeA6BJL4cESn5XkSI4kIQYCuk1PwBARIBLTEJy8mESEIEClkuAgErkFAW3ykIgQBukZewQsQIBGAAJEw4SIQAAFtAlbiI+mC0AFpRxnzgcCiBCBAiwYGZvkRGL1vInmi4ufFnitBgPaMG6xWIJCFZ/ToN79jAiFSgF5N8Xw+yS8bzq5+HAdrV8W6eNYojLsvAarw1IQ0X3a7L/1XzyFAyIRbEdBq+dEVydNGKxYjS7jvBKEDGhHF59MEtO+4EKLpUPz9rtdo+zs/+/+RECANiphDTEBbfEqDIoQoFfDZudTZ/3vXZj/quVpjxIF4m+DyAjR6ulGDXPWQkerHqvZrJaxknlWTXepTGl/GPedK/b/0dy0oPYHJn1mK0KoxEW3BZr9NmwK5wiGjxP6czBF3Y0kheYz1SvacRx43g1okzkRjJDo9wcpxsRIhr5i4bcE0HeIaLS2i2SczvXU9faB2aT17LYvWcvt15pMH+1IYer9TOv16PGWMJOc1a5ViByceUx2QhUMcoykQWtdY2F52QxaFbSGYNR/u+xsRfCNvAL1znRzzUQd01uVQx6ya89IbAluAdizgMvDWTwI0hdRDeLSFyDI/RkWoyb5eSypAZdd6dmZkuQXzjgknDrcRIK8gcOD3CsrL3jMbJN1QpN3JF4ntI4Hb9XPvmHBq4BYC5BkADvzVtjHZHkkRe7Ju8ZPYv6vIrHQz49QAS4A8kotjPCVZPGwu7ZDa722vdhGvYL80BmdMemc1nEPl1lYsrdna5lHyHAIkpfQ2XjN5IopBan+EzZrbsBXsl8ZAIkC9s5zRgTMEaCAiHsmllTwetkqfALQS3fqgnHKvmN3GRHHX7EIhQJQMaV/DqWHyFswzsTgOtDB4v4uS7NCw25Nzi53Ujwj2HgJU82o9fm9tp9ABvc84CJBM7F9GSws3J250ByT1I1pEpfZHdUCKqfhuKq+YcNlfUoC8YFvcdSNsr7OVm0QrbiVnt5AtEaAeQvfOgPINJl/j+YVUr7zi5g4ESOm2wwXfS/TdO6CyCCN80YqFUmosMQ0EiBEGaQJ5wc4uSe0t0XjbfhaWnf3RtJ2Rsltcap1bM+zRASmkzgz43rLWiTJy2cKftKZHN6Rt+4jVbp/n95A0Y5GYp5+Z70FCgIQZZJHwkQJk4Y/HlkxSBMIU2HK4hhBpMIcACdNH+7CzLFavrsFiK9k749L0S6MIhCmw9fAZIdJkDgESpI9lt+ApRJoJRcVZfiVhZjsQYTPVtx2vK+Mxsn9mq9WaEwI0ot343EN8yqWlBVu7kQt4du8+ia07jFoEmgVg4QfmpBMgC1Ca0uNsQquwLd/G1bKRHqbXK6mF2pofBTxLHuO0CVxWgKzEMlp8tBMA84FAJIHLCpBFxwbxiUxVrH1FAiwBsijqEqpFgc+c8rfOS7B1uWIJwKdIApcXoAx35hAXT1oiUxNr34EAW4CsuiCL7qcVQOohLjqeO5QAfIwkMCVA2iLkKT6RsLE2CIDAK4FpAcoilKfjvky24nsoSA4QAAFfAiIBKk2lbmvyGGxvfAON1UBgRQJqArSic7AJBEBgbQJ/AOoMmm0ZeBaqAAAAAElFTkSuQmCC);
    width: calc(2 * 6 * 48px);
    height: calc(2 * 48px);
    background-size: calc(2 * 6 * 48px) calc(2 * 48px) !important; 
}
.wrapper .dog {
    position: absolute;
    width: calc(2 * 48px);
    height: calc(2 * 48px);
    animation: fade-in forwards 1s;
    transition: 0.5s;
    z-index: 9999;
    bottom: 50px;
    left: 50px;
}
@keyframes fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
}
.wrapper .head {
    position: absolute;
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALoAAAAgCAYAAACl82LUAAAAAXNSR0IArs4c6QAAA+hJREFUeF7tmtuV1DAMhiddQSdQABS1FACdQFezx4cVR2h1+X1LnJXnaSbjRLfPkhz7eOzP9kACDxwJbNwmbg88bgP6y5dPz++//txG383WWh64BTgF8m8/fz9+fP382LCvBdBdtNmgN0Rqteqymj4NLp1+CwR6rSNrx3tWUjanMVdn9dWqy2r69BJbyw46PgRdOrL8towpbcVox3N5pX0pnwK7/F6u0Wdke0PyqXWydJghuzzT8rdm/0i7e4FtuX8ma1Wgk3M9IwjCEZlXZnMpl8viuhH0FHiCpQYEDjivJlKO9ImU3Rrwcl/kbz7hKQHU2Nii28x7OOiR7TzhIay5oGttg6fAKMdrkGkOtkC3xiIQeLI1+yx/IM6XeqJ2WxOPrh/HESawmcC2PHs2a6pDrJklA21lWBmImqBHWbwmu2r6ebCjstFAjrA78rmlSyTb6m21695Yq2VDe+ezWHsH+uhgczCjjFojuya7IjrUyEZBR9sJS3ZNxaqtYlo7Z12jtRePnwcy/RfBfqbP/wN9luCzQdMWqx50V9pd9JoFemQzB1iDU4Kq/S4yvAngVQOkD69JKnysrGgfHnQKtgwId8qVoEeQR/14BILVwnAAve/y+VpVlvej91wC+uxge1k9q2wkm7eCLquaBBTp0aOMTrpZE4Xs02TPhFxj7V9Gzwrbqna3LkK1iaFl9V7QqZ9vaV2u8PkG/e0cTdQCRP/T+3MvU8nXfl7Ae0H3+tXIltn/b9Ane9jKbCPKqAe6tWt7RcAnuxh6vLcuKQ+oiUfkd2qbUmV0bSPlTNi0Ml8TVIQiHnhvl9rrv2sWl1YLY/Xn5frz+VSPkSBVUfoguodingZ0bwNlFuzRTnGrXCu4vHLwszDRmxIJpda/I9fQxasFujWRNbuQSV/GvAPdm2noQ5Fx1vZ0a9ARmWXMFaCTXNJRA6426PyZ3vGDDfpfry+V0SmjjC7jfBJE5z9mTzTudK7XLLmUBa3daN5yyImIZG+tNUEzemSzthBHr3Hf8uS2zIbRTNijcx/knCgAaPXQxnkTrVeutRNsTa4I0lGge/6qeeNkHYXw3ky5O6MfDbYoq2mBoEw3qrqgOvTKlX17NLnRxShPAlavb7Vm1rt6JLFYfTnSr2u2m6cXizIjgo0GWpbzHvktMiX0PeBZrxORitAjl/fv0QE6RJczxvTaK1uV8luzPTyP3mtsj8PJCXT4CNWlR6aX5RH5I2VrfbSlw0i5iJ2jx1ivKb21BHUgVkXhOt7ugP5oB+/n5fDABj1HnNNbuUFPj0AOB2zQc8Q5vZUb9PQI5HDABj1HnNNbuUFPj0AOB7wCKr30XX1N2MUAAAAASUVORK5CYII=);
    width: calc(2 * 6 * 31px);
    height: calc(2 * 32px);
    background-size: calc(2 * 6 * 31px) calc(2 * 32px) !important; 
}
.wrapper .head-wrapper.happy > .head {
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALoAAAAgCAYAAACl82LUAAAAAXNSR0IArs4c6QAABCpJREFUeF7tmtlx3DAMQFddxZ0kBSS/Hlfi8W9cQNJJ0tV6qAl2YAgnRVBSyP2yJYq4HkHwWG7zNz0wgAeWAWycJk4P3C4D+s+vX+4/fv+9jL6TrXN54BLgFMi///pze//2dJuwnwugq2gzQa+I1Nlml7PpU+HS9E9coEcdGW2vWQnZHNocndXPNrucTZ+9xEbZ8bY3QaeOLP9LxpSyorXjsbxSvpRfgZ3+XZ7Br2V5A/KhdJJ0yJBd+pT8zdnf0u69wNZ8n8laCHRwrmYEQNgi89JsTuViWVg3gB4CD7BEQMCA49mEyqE+obJrA16+s/yNBzwkgIiNNbplfoNBt2zHCc/Dmgo6VzZoCrRyPAcZ52AJdKmtBwJNNmef5A+P86meXrulgQfPl2UxE1gmsDV9Z7PGOkQaWTTQUoalgYgE3crikezK6afB7pXtDWQLuy2fS7pYsqXalnuutZVKNm/t3Iu1Deitg43BtDJqRHYku3p0iMj2gu4tJyTZkRkrOotx5Zz0DNZeOH4ayPDOgr2nzz+BniW4N2jcYlWD7ki7i15ZoFs2Y4A5OCmo3P9FhjYAtNnAU4dHkgpuS2e0/x50CPa6uHt+vb2/vWwOnY4E3YLcqsctEKQSBgOo/U3752Zl+r33m0NAzw62ltV7yF6z/PPrqgaGvYdsK7NyAa8pzWhGw1uQFFBPjW5ldJAnDRSYrTjZmZBzrD0y+pEB7ymbm2p7Ox0Dou3a7NGLlm+tQYd6vqZ06RlvsHs40LOyC+yfa3DSbT8t4LW7LdFFqVX6ZLyfoGd4FfXJ1autnK6BLp3atpJtuc3aarS+b/1eW5esa6l/J+AeuZbfh8zo3EFKT9h6lE048NoptVZ/RxaXUgkj1efl+f1+Z6+ReGZFCr/1DcR8mNJFy2pZsFsnxbVypeDimUNbiHIQagtKbVFZs7UogS5lcM4uT7YvbTagayPN26mnnXQ8XRt0j0xt1wOCGJkuvTJBLm5P1wjRoOM+PQtZz/YiBZnbkfE88+7SRG1uCno2aBBszfFZsOGRLUGaab+0tZk1yHDG1xIL9QW+BOfZpbHA1rYwI3emYHBzF+isfjY1epbTsTOtRVEWbJZc0DFL/iNQSQdWm5Ng5ryAQu2t0bFvLPglsLnkEtlxgkRRzj+s26NSQj3VXZfWgw0ym3XHBgcCFletZhesgwbCXrmfpvdOoEO8wH/WQOAGm1Z+QXs46FsTxtvLo5vIzVHx9mLprUWwj4CtRiYXhFofSNuJntp+L/C4ho8McI9uGW0se3HZB6Bbg4Oz27yPvte4Pc4GJ8DlI68ue2RK0+wRsvHWnSW/tc2WvNbvrW1KbdaQ3mEdL3dBv7WDZ39jeGCCPkach7dygj48AmM4YII+RpyHt3KCPjwCYzhggj5GnIe3coI+PAJjOOADdCQTbGUe9fwAAAAASUVORK5CYII=);
}
.wrapper .head-wrapper.happy {
    animation: infinite 0.5s pant;
}
@keyframes pant {
    0%, 100% { transform: translateY(-1px); }
    50% { transform: translateY(1px); }
}
.wrapper .head-wrapper.flip.happy {
    animation: infinite 0.5s pant-flip;
}
@keyframes pant-flip {
    0%, 100% { transform: translateY(-1px) scale(-1, 1); }
    50% { transform: translateY(1px) scale(-1, 1); }
}
.wrapper .tail {
    position: absolute;
    background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAADZJREFUKFNjZICCWREW/2FsEJ224gQjiAYTIMnU5ceR5RlmR1qCFTFik4SpBCmihwKCjiTkTQB1sCqti9mJ/QAAAABJRU5ErkJggg==);
    width: calc(2 * 8px);
    height: calc(2 * 8px);
    background-size: calc(2 * 8px) !important; 
}
.wrapper .tail-wrapper {
    position: absolute;
    width: calc(2 * 8px);
    height: calc(2 * 8px);
    transition: 0.15s;
}
.wrapper .body-wrapper {
    position: absolute;
    width: calc(2 * 48px);
    height: calc(2 * 48px);
    overflow: hidden;
}
.wrapper .body-wrapper,
.head-wrapper {
    z-index: 1; 
} 
.wrapper .walk-1 {
    animation: infinite 0.4s walking;
    animation-delay: 0;
}
.wrapper .walk-2 {
    animation: infinite 0.4s walking;
    animation-delay: 0.2s;
}
@keyframes walking {
    0%, 100% { transform: translateY(-4px); }
    50% { transform: translateY(0); }
}
.wrapper .wag {
    animation: infinite 0.5s wag;
}
@keyframes wag {
    0%, 100% { transform: translateX(-2px); }
    50% { transform: translateX(2px); }
}
.wrapper .head-wrapper {
    position: absolute;
    top: 6px;
    left: 16px;
    width: calc(2 * 31px);
    height: calc(2 * 32px);
    overflow: hidden;
}
.wrapper .flip {
    transform: scale(-1, 1);
}
.wrapper .img-bg {
    image-rendering: pixelated;
    background-repeat: no-repeat !important;
}
.wrapper .indicator {
    position: fixed;
    top: 10px;
    left: 10px;
    color: #9a5838;
}
.wrapper .d-none {
    display: none;
}
.wrapper .indicator {
    position: fixed;
    top: 10px;
    right: 10px;
}
.wrapper .marker {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    position: absolute;
    transition: 0.5s;
    z-index: 100;
    margin-top: -5px;
    margin-left: -5px;
}
.wrapper .red { background-color: rgb(255, 64, 0); }
.wrapper .green { background-color: rgb(42, 239, 190); }
.wrapper .blue { background-color: rgb(0, 140, 255); }
  • 小狗交互

function init() { 
  const elements = {
    body: document.body,
    wrapper: document.querySelector('.wrapper'),
    dog: document.querySelector('.dog'),
    marker: document.querySelectorAll('.marker'),
  }

  const animationFrames = {
    rotate: [[0], [1], [2], [3], [5], [3, 'f'], [2, 'f'], [1, 'f']]
  }

  const directionConversions = {
    360: 'up',
    45: 'upright',
    90: 'right',
    135: 'downright',
    180: 'down',
    225: 'downleft',
    270: 'left',
    315: 'upleft',
  }

  const angles = [360, 45, 90, 135, 180, 225, 270, 315]
  const defaultEnd = 4
  //  A ---- A  ________ ________
  // |         |         |        |
  // | ^     ^ |         |        |
  //  ____^___  _________|________|
  //            | |  | |  | |  | |
  //             1    2    3    4
  //             L    R    L    R
  const partPositions = [
    { //0
      leg1: { x: 26, y: 43 },
      leg2: { x: 54, y: 43 },
      leg3: { x: 26, y: 75 },
      leg4: { x: 54, y: 75 },
      tail: { x: 40, y: 70, z: 1 },
    }, 
    { //1
      leg1: { x: 33, y: 56 },
      leg2: { x: 55, y: 56 },
      leg3: { x: 12, y: 72 },
      leg4: { x: 32, y: 74 },
      tail: { x: 20, y: 64, z: 1 },
    }, 
    { //2
      leg1: { x: 59, y: 62 },
      leg2: { x: 44, y: 60 },
      leg3: { x: 25, y: 64 },
      leg4: { x: 11, y: 61 },
      tail: { x: 4, y: 44, z: 1 },
    }, 
    { //3
      leg1: { x: 39, y: 63 },
      leg2: { x: 60, y: 56 },
      leg3: { x: 12, y: 52 },
      leg4: { x: 28, y: 50 },
      tail: { x: 7, y: 21, z: 0 },
    }, 
    { //4
      leg1: { x: 23, y: 54 },
      leg2: { x: 56, y: 54 },
      leg3: { x: 24, y: 25 },
      leg4: { x: 54, y: 25 },
      tail: { x: 38, y: 2, z: 0 },
    }, 
    { //5
      leg1: { x: 21, y: 58 },
      leg2: { x: 41, y: 64 },
      leg3: { x: 53, y: 50 },
      leg4: { x: 69, y: 53 },
      tail: { x: 72, y: 22, z: 0 },
    }, 
    { //6
      leg1: { x: 22, y: 59 },
      leg2: { x: 30, y: 64 },
      leg3: { x: 56, y: 60 },
      leg4: { x: 68, y: 62 },
      tail: { x: 78, y: 40, z: 0 },
    }, 
    { //7
      leg1: { x: 47, y: 45 },
      leg2: { x: 24, y: 53 },
      leg3: { x: 68, y: 68 },
      leg4: { x: 47, y: 73 },
      tail: { x: 65, y: 65, z: 1 },
    }, 
  ]

  const control = {
    x: null,
    y: null,
    angle: null,
  }

  const distance = 30
  const nearestN = (x, n) => x === 0 ? 0 : (x - 1) + Math.abs(((x - 1) % n) - n)
  const px = num => `${num}px`
  const radToDeg = rad => Math.round(rad * (180 / Math.PI))
  const degToRad = deg => deg / (180 / Math.PI)
  const overlap = (a, b) =>{
    const buffer = 20
    return Math.abs(a - b) < buffer
  }

  const rotateCoord = ({ angle, origin, x, y }) =>{
    const a = degToRad(angle)
    const aX = x - origin.x
    const aY = y - origin.y
    return {
      x: (aX * Math.cos(a)) - (aY * Math.sin(a)) + origin.x,
      y: (aX * Math.sin(a)) + (aY * Math.cos(a)) + origin.y,
    }
  }

  const setStyles = ({ target, h, w, x, y }) =>{
    if (h) target.style.height = h
    if (w) target.style.width = w
    target.style.transform = `translate(${x || 0}, ${y || 0})`
  }

  const targetAngle = dog =>{
    if (!dog) return
    const angle = radToDeg(Math.atan2(dog.pos.y - control.y, dog.pos.x - control.x)) - 90
    const adjustedAngle = angle < 0 ? angle + 360 : angle
    return nearestN(adjustedAngle, 45)
  }

  const reachedTheGoalYeah = (x, y) =>{
    return overlap(control.x , x) && overlap(control.y, y)
  }

  const positionLegs = (dog, frame) => {
    ;[5, 7, 9, 11].forEach((n, i) => {
      const { x, y } = partPositions[frame][`leg${i + 1}`]
      setStyles({
        target: dog.childNodes[n],
        x: px(x), y: px(y),
      })
    })
  }

  const moveLegs = dog => {
    ;[5, 11].forEach(i => dog.childNodes[i].childNodes[1].classList.add('walk-1'))
    ;[7, 9].forEach(i => dog.childNodes[i].childNodes[1].classList.add('walk-2'))
  }

  const stopLegs = dog => {
    ;[5, 11].forEach(i => dog.childNodes[i].childNodes[1].classList.remove('walk-1'))
    ;[7, 9].forEach(i => dog.childNodes[i].childNodes[1].classList.remove('walk-2'))
  }

  const positionTail = (dog, frame) => { 
    setStyles({
      target: dog.childNodes[13],
      x: px(partPositions[frame].tail.x), y: px(partPositions[frame].tail.y),
    })
    dog.childNodes[13].style.zIndex = partPositions[frame].tail.z
    dog.childNodes[13].childNodes[1].classList.add('wag')
  }

  const animateDog = ({ target, frameW, currentFrame, end, data, part, speed, direction }) => {
    const offset = direction === 'clockwise' ? 1 : -1


    target.style.transform = `translateX(${px(data.animation[currentFrame][0] * -frameW)})`
    if (part === 'body') {
      positionLegs(data.dog, currentFrame)
      moveLegs(data.dog)
      positionTail(data.dog, currentFrame) 
    } else {
      target.parentNode.classList.add('happy')
    }
    data.angle = angles[currentFrame]
    data.index = currentFrame

  target.parentNode.classList[data.animation[currentFrame][1] === 'f' ? 'add' : 'remove']('flip')

    let nextFrame = currentFrame + offset
    nextFrame = nextFrame === -1 
      ? data.animation.length - 1
      : nextFrame === data.animation.length
        ? 0
        : nextFrame
    if (currentFrame !== end) {
      data.timer[part] = setTimeout(()=> animateDog({
        target, data, part, frameW, 
        currentFrame: nextFrame, end, direction,
        speed,
      }), speed || 150)
    } else if (part === 'body') {
      // 结束
      control.angle = angles[end]
      data.walk = true
      setTimeout(()=> {
        stopLegs(data.dog)
      }, 200)
      setTimeout(()=> {
        document.querySelector('.happy')?.classList.remove('happy')
      }, 5000)
    }
  }

  const triggerDogAnimation = ({ target, frameW, start, end, data, speed, part, direction }) => {
    clearTimeout(data.timer[part])
    data.timer[part] = setTimeout(()=> animateDog({
      target, data, part, frameW,
      currentFrame: start, end, direction,
      speed,
    }), speed || 150)
  }

  const getDirection = ({ pos, facing, target }) =>{
    const dx2 = facing.x - pos.x
    const dy1 = pos.y - target.y
    const dx1 = target.x - pos.x
    const dy2 = pos.y - facing.y

    return dx2 * dy1 > dx1 * dy2 ? 'anit-clockwise' : 'clockwise'
  }

  const turnDog = ({ dog, start, end, direction }) => {
    triggerDogAnimation({ 
      target: dog.dog.childNodes[3].childNodes[1],
      frameW: 31 * 2,
      start, end,
      data: dog,
      speed: 100,
      direction,
      part: 'head'
    }) 
    
    setTimeout(()=>{
      triggerDogAnimation({ 
        target: dog.dog.childNodes[1].childNodes[1],
        frameW: 48 * 2,
        start, end,
        data: dog,
        speed: 100,
        direction,
        part: 'body'
      }) 
    }, 200)
  }

  const createDog = () => {
    const { dog } = elements
    const { width, height, left, top } = dog.getBoundingClientRect()
    dog.style.left = px(left)
    dog.style.top = px(top)

    positionLegs(dog, 0)
    const index = 0

    const dogData = {
      timer: {
        head: null, body: null, all: null,
      },
      pos: {
        x: left + (width / 2),
        y: top + (height / 2),
      },
      actualPos: { x: left, y: top },
      facing: {
        x: left + (width / 2),
        y: top + (height / 2) + 30,
      },
      animation: animationFrames.rotate,
      angle: 360,
      index,
      dog,
    }
    elements.dog = dogData

    turnDog({
      dog: dogData,
      start: index, end: defaultEnd,
      direction: 'clockwise'
    })
    positionTail(dog, 0)
  }

  const checkBoundaryAndUpdateDogPos = (x, y, dog, dogData) =>{
    const lowerLimit = -40 // 离边缘距离
    const upperLimit = 40
    if (x > lowerLimit && x < (elements.body.clientWidth - upperLimit)){
      dogData.pos.x = x + 48
      dogData.actualPos.x = x
    } 
    if (y > lowerLimit && y < (elements.body.clientHeight - upperLimit)){
      dogData.pos.y = y + 48
      dogData.actualPos.y = y
    }
    dog.style.left = px(x)
    dog.style.top = px(y)
  }

  const positionMarker = (i, pos) => {
    if(elements.marker[i]){
      elements.marker[i].style.left = px(pos.x)
      elements.marker[i].style.top = px(pos.y)
    }
  }

  const moveDog = () =>{
    clearInterval(elements.dog.timer.all)
    const { dog } = elements.dog

    elements.dog.timer.all = setInterval(()=> {
      const { left, top } = dog.getBoundingClientRect()
      const start = angles.indexOf(elements.dog.angle)
      const end = angles.indexOf(targetAngle(elements.dog))

      // 停止
      if (reachedTheGoalYeah(left + 48, top + 48)) {
        clearInterval(elements.dog.timer.all)
        const { x, y } = elements.dog.actualPos
        dog.style.left = px(x)
        dog.style.top = px(y)
        stopLegs(dog)
        turnDog({
          dog: elements.dog,
          start,
          end: defaultEnd,
          direction: 'clockwise'
        })
        return
      }

      let { x, y } = elements.dog.actualPos
      const dir = directionConversions[targetAngle(elements.dog)]
      if (dir !== 'up' && dir !== 'down') x += (dir.includes('left')) ? -distance : distance
      if (dir !== 'left' && dir !== 'right') y += (dir.includes('up')) ? -distance : distance

      positionMarker(0, elements.dog.pos)
      positionMarker(1, control)

      const { x: x2, y: y2 } = rotateCoord({
        angle: elements.dog.angle,
        origin: elements.dog.pos, 
        x: elements.dog.pos.x,
        y: elements.dog.pos.y - 100,
      })
      elements.dog.facing.x = x2
      elements.dog.facing.y = y2
      positionMarker(2, elements.dog.facing)

      if (start === end) {
        elements.dog.turning = false
      }

      if (!elements.dog.turning && elements.dog.walk) {
        if (start !== end) {
          elements.dog.turning = true

          const direction = getDirection({ 
            pos: elements.dog.pos,
            facing: elements.dog.facing,
            target: control,
          })
          turnDog({
            dog: elements.dog,
            start, end, direction,
          })
        } else {
          checkBoundaryAndUpdateDogPos(x, y, dog, elements.dog)
          moveLegs(dog)
        }
      }
    }, 200)
  }

  createDog()
  const triggerTurnDog = () => {
    const dog = elements.dog
    dog.walk = false
    control.angle = null

    const direction = getDirection({ 
      pos: dog.pos,
      facing: dog.facing,
      target: control,
    })
    const start = angles.indexOf(dog.angle)
    const end = angles.indexOf(targetAngle(dog))
    turnDog({
      dog,
      start, end, direction
    })
  }

  elements.body.addEventListener('mousemove', e =>{
    control.x = e.pageX
    control.y = e.pageY
    triggerTurnDog()
  })
  elements.body.addEventListener('click', moveDog)

}

window.addEventListener('DOMContentLoaded', init)

人来人往

这个是我直接从别人那里抄过来的,逛博客看到这个设计真的驻足了好久,很喜欢。后来发现我逛的那位博主不是原创,我记得我在ins还是Youtube上看到原创了,但是没点关注找不到了。

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>people</title>
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,minimal-ui">
		<style>
			body, html { height: 100%; }
			body { margin: 0 }
			#canvas { width: 100%; height: 100% }
			body::-webkit-scrollbar { display: none }
		</style>
	</head>
	<body>
		<canvas id="canvas" width="786" height="259"></canvas>
        <!-- 引入插件(需要的话自行下载引入)-->
		<script src="https://yaoyuan.vip/js/Plugins/gsap.min.js"></script>
		<script>
      "use strict";
      function _toConsumableArray(e) {
          return _arrayWithoutHoles(e) || _iterableToArray(e) || _unsupportedIterableToArray(e) || _nonIterableSpread()
      }
      function _nonIterableSpread() {
          throw new TypeError(
              "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
              )
      }
      function _unsupportedIterableToArray(e, r) {
          if (e) {
              if ("string" == typeof e) return _arrayLikeToArray(e, r);
              var t = Object.prototype.toString.call(e).slice(8, -1);
              return "Object" === t && e.constructor && (t = e.constructor.name), "Map" === t || "Set" === t ? Array.from(e) :
                  "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(e, r) : void 0
          }
      }
      
      function _iterableToArray(e) {
          if ("undefined" != typeof Symbol && null != e[Symbol.iterator] || null != e["@@iterator"]) return Array.from(e)
      }
      
      function _arrayWithoutHoles(e) {
          if (Array.isArray(e)) return _arrayLikeToArray(e)
      }
      
      function _arrayLikeToArray(e, r) {
          (null == r || r > e.length) && (r = e.length);
          for (var t = 0, a = new Array(r); t < r; t++) a[t] = e[t];
          return a
      }
      
      function _classCallCheck(e, r) {
          if (!(e instanceof r)) throw new TypeError("Cannot call a class as a function")
      }
      
      function _defineProperties(e, r) {
          for (var t = 0; t < r.length; t++) {
              var a = r[t];
              a.enumerable = a.enumerable || !1, a.configurable = !0, "value" in a && (a.writable = !0), Object
                  .defineProperty(e, a.key, a)
          }
      }
      
      function _createClass(e, r, t) {
          return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), e
      }
      var config = { src: "https://npm.elemecdn.com/guli-heo/others/open-peeps-sheet.png", rows: 15, cols: 7 },
          randomRange = function(e, r) {
              return e + Math.random() * (r - e)
          },
          randomIndex = function(e) {
              return 0 | randomRange(0, e.length)
          },
          removeFromArray = function(e, r) {
              return e.splice(r, 1)[0]
          },
          removeItemFromArray = function(e, r) {
              return removeFromArray(e, e.indexOf(r))
          },
          removeRandomFromArray = function(e) {
              return removeFromArray(e, randomIndex(e))
          },
          getRandomFromArray = function(e) {
              return e[0 | randomIndex(e)]
          },
          resetPeep = function(e) {
              var r, t, a = e.stage,
                  n = e.peep,
                  o = .5 < Math.random() ? 1 : -1,
                  i = 100 - 250 * gsap.parseEase("power2.in")(Math.random()),
                  s = a.height - n.height + i;
              return 1 == o ? (r = -n.width, t = a.width, n.scaleX = 1) : (r = a.width + n.width, t = 0, n.scaleX = -1), n.x =
                  r, n.y = s, {
                      startX: r,
                      startY: n.anchorY = s,
                      endX: t
                  }
          },
          normalWalk = function(e) {
              var r = e.peep,
                  t = e.props,
                  a = (t.startX, t.startY),
                  n = t.endX,
                  o = gsap.timeline();
              return o.timeScale(randomRange(.5, 1.5)), o.to(r, {
                  duration: 10,
                  x: n,
                  ease: "none"
              }, 0), o.to(r, {
                  duration: .25,
                  repeat: 40,
                  yoyo: !0,
                  y: a - 10
              }, 0), o
          },
          walks = [normalWalk],
          Peep = function() {
              function e(r) {
                  var t = r.image,
                      a = r.rect;
                  _classCallCheck(this, e), this.image = t, this.setRect(a), this.x = 0, this.y = 0, this.anchorY = 0, this
                      .scaleX = 1, this.walk = null
              }
              return _createClass(e, [{
                  key: "setRect",
                  value: function(e) {
                      this.rect = e, this.width = e[2], this.height = e[3], this.drawArgs = [this.image]
                          .concat(_toConsumableArray(e), [0, 0, this.width, this.height])
                  }
              }, {
                  key: "render",
                  value: function(e) {
                      e.save(), e.translate(this.x, this.y), e.scale(this.scaleX, 1), e.drawImage.apply(e,
                          _toConsumableArray(this.drawArgs)), e.restore()
                  }
              }]), e
          }(),
          img = document.createElement("img");
      img.onload = init, img.src = config.src;
      var canvas = document.querySelector("#canvas"),
          ctx = canvas.getContext("2d"),
          stage = {
              width: 0,
              height: 0
          },
          allPeeps = [],
          availablePeeps = [],
          crowd = [];
      
      function init() {
          createPeeps(), resize(), gsap.ticker.add(render), window.addEventListener("resize", resize)
      }
      
      function createPeeps() {
          for (var e = config.rows, r = config.cols, t = e * r, a = img.naturalWidth / e, n = img.naturalHeight / r, o =
              0; o < t; o++) allPeeps.push(new Peep({
              image: img,
              rect: [o % e * a, (o / e | 0) * n, a, n]
          }))
      }
      
      function resize() {
          stage.width = canvas.clientWidth, stage.height = canvas.clientHeight, canvas.width = stage.width * devicePixelRatio,
              canvas.height = stage.height * devicePixelRatio, crowd.forEach((function(e) {
                  e.walk.kill()
              })), crowd.length = 0, availablePeeps.length = 0, availablePeeps.push.apply(availablePeeps, allPeeps),
              initCrowd()
      }
      
      function initCrowd() {
          for (; availablePeeps.length;) addPeepToCrowd().walk.progress(Math.random())
      }
      
      function addPeepToCrowd() {
          var e = removeRandomFromArray(availablePeeps),
              r = getRandomFromArray(walks)({
                  peep: e,
                  props: resetPeep({
                      peep: e,
                      stage: stage
                  })
              }).eventCallback("onComplete", (function() {
                  removePeepFromCrowd(e), addPeepToCrowd()
              }));
          return e.walk = r, crowd.push(e), crowd.sort((function(e, r) {
              return e.anchorY - r.anchorY
          })), e
      }
      
      function removePeepFromCrowd(e) {
          removeItemFromArray(crowd, e), availablePeeps.push(e)
      }
      
      function render() {
          canvas.width = canvas.width, ctx.save(), ctx.scale(devicePixelRatio, devicePixelRatio), crowd.forEach((function(e) {
              e.render(ctx)
          })), ctx.restore()
      }
      </script> 
	</body>
</html>

相册(反向滚动)

自己对接下相册数据列表,点击后操作需要自己按照需求去添加。目前数据格式是这样的

[{
“link”: “图片链接”,
“desc”: “图片表述”,
“type”: “风景、西安”
},{
“link”: “xx”,
“desc”: “xxx😀”,
“type”: “xx”
}]

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>people</title>
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,minimal-ui">
		<style>
			html, body { height: 100%; }
      body { overflow: hidden; }
      .photoWall {
        position: absolute;
        top: 0;
        left: 0;
        pointer-events: none;
      }
      .scrollsection {
        padding: 10vh 10vh 10vh 10vmax;
        min-width: 99999999999999vh;
        pointer-events: none !important;
      }
      .scrollsection > .item {
        display: inline-block;
        position: relative;
        margin: 0 -30vh 0 3vh;
      }
      .scrollsection > .item::before {
        content: "";
        display: inline-block;
        vertical-align: middle;
        height: 100%;
      }
      .scrollsection > .item.-big {
        height: 80vh;
        width: 60vh;
      }
      .scrollsection > .item.-big.-horizontal {
        height: 60vh;
        width: 80vh;
      }
      .scrollsection > .item.-normal {
        height: 60vh;
        width: 45vh;
        z-index: 100;
      }
      .scrollsection > .item.-normal:nth-of-type(3n) {
        bottom: 5vh;
      }
      .scrollsection > .item.-normal:nth-of-type(4n) {
        bottom: -5vh;
      }
      .scrollsection > .item.-small {
            height: 40vh;
        width: 30vh;
        z-index: 200;
      }
      .scrollsection > .item.-small.-horizontal {
        height: 30vh;
        width: 40vh;
      }
      .scrollsection > .item.-small:nth-of-type(3n) {
        bottom: 13vh;
      }
      .scrollsection > .item.-small:nth-of-type(4n) {
        bottom: -13vh;
      }
      .scrollsection > .item > .image {
        height: 100%;
        width: 100%;
        position: absolute;
        top: 0;
        left: 0;
        filter: grayscale(1);
        opacity: 0;
        pointer-events: none;
        transform: translateX(0) translateY(0) scale(1);
        transition: filter 0.3s ease, opacity 1s ease, transform 1s ease;
        object-fit: contain;
      }
      .scrollsection > .item:nth-of-type(4n) > .image {
        transform: translateX(-30vh) translateY(-30vh) scale(0.8);
        transition-delay: 0;
      }
      .scrollsection > .item:nth-of-type(4n - 1) > .image {
        transform: translateX(30vh) translateY(30vh) scale(0.7);
        transition-delay: 0.05s;
      }
      .scrollsection > .item:nth-of-type(4n - 2) > .image {
        transform: translateX(-30vh) translateY(30vh) scale(0.8);
        transition-delay: 0.1s;
      }
      .scrollsection > .item.-normal.-horizontal {
        height: 45vh;
        width: 60vh;
      }
      .scrollsection > .item:nth-of-type(4n - 3) > .image {
        transform: translateX(-30vh) translateY(-30vh) scale(0.8);
        transition-delay: 0.15s;
      }
      .scrollsection > .item > .image.-active {
        transform: translateX(0) translateY(0) scale(1);
        opacity: 0.8;
        pointer-events: auto;
        transition: filter 0.3s ease, opacity 1s ease, transform 1s ease;
      }
      .scrollsection > .item > .image.-clicked {
        transform: translateX(0) translateY(0) scale(5);
        opacity: 0;
        pointer-events: auto;
        transition: filter 0.3s ease, opacity 1s ease, transform 1s ease;
      }
      .scrollsection > .item > .image.-active:hover {
        height: 100%;
        width: 100%;
        position: absolute;
        top: 0;
        left: 0;
        filter: grayscale(0);
        opacity: 1;
        cursor: pointer;
        transform: translateX(0) translateY(0) scale(1.2);
        z-index: 150;
      }
      .btn {
        margin-top: 0;
        margin-bottom: 10px;
        display: inline-block;
        position: relative;
        background: linear-gradient(135deg, #6e87d6, #8b63e4);
        color: #fff;
        font-size: 16px;
        font-weight: bold;
        text-align: center;
        text-decoration: none;
        border-radius: 6px;
        box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
        overflow: hidden;
        transition: all 0.3s ease;
        border: none;
      }
      .btn a {
        padding: 12px 15px !important;
        line-height: 1.5rem !important;
        color: #fff !important;
        font-size: 16px !important;
        font-weight: bold;
      }
      .btn:hover {
        background: linear-gradient(135deg, #8b63e4, #6e87d6);
        box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
        transform: translateY(-2px);
      }
      .ripple {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        pointer-events: none;
        overflow: hidden;
      }
      .ripple::before {
        content: "";
        display: block;
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        background-image: radial-gradient(circle, #fff 10%, transparent 10.01%);
        background-repeat: no-repeat;
        background-position: 50%;
        transform: scale(10, 10);
        opacity: 0;
        transition: transform 0.5s ease-out, opacity 1s ease-out;
      }
      .ripple-effect {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          width: 0;
          height: 0;
          border-radius: 50%;
          background-color: rgba(255, 255, 255, 0.7);
          animation: ripple-animation 1s;
      }
      @keyframes ripple-animation {
        from {
          width: 0;
          height: 0;
          opacity: 0.7;
        }
        to {
          width: 300px;
          height: 300px;
          opacity: 0;
        }
      }
		</style>
	</head>
	<body>
    <div class="photoWall">
        <div class='scroll-animations-example' data-scroll-container>
            <div class='scrollsection' data-scroll-section></div>
        </div>
      </div>
    <!-- 引入插件(需要的话自行下载引入)-->
		<script src="https://yaoyuan.vip/js/Plugins/locomotive-scroll.min.js"></script>
		<script>
      let scrollsection = document.querySelector('.scrollsection')
      function getData() {
        // 获取相册列表,这里数据以自己的方式对接,只做示例
        return new Promise((resolve, reject) => {
          fetch('https://yaoyuan.vip/photo/photoList.json')
          .then(response => response.json())
          .then(res => {
            renderData(res)
            const example = new Example({
                root: document.querySelector('.scroll-animations-example')
            });
          });
        })
      }
      function renderData(data) {
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < data.length; i++) {
          const div = document.createElement('div');
          //const scroll = document.createElement('scroll-animations-example')
          const img = document.createElement('img');
          //scroll.setAttribute('data-scroll-section')
          div.className = random('className')
          div.setAttribute('data-scroll', true)
          div.setAttribute('data-scroll-speed', i==0?'item -normal -horizontal': random('number', 1, 5))
          img.className = 'image'
          img.src = data[i].link;
          img.style.maxWidth = 600;
          div.appendChild(img);
          //scroll.appendChild(div);
          fragment.appendChild(div);
        }
        scrollsection.textContent  = ""
        scrollsection.appendChild(fragment);
      }
      function random(type, min, max) {
        let r = Math.floor(Math.random() * (max - min + 1) + min)
        let arr =['item -small -horizontal','item -big -horizontal','item -normal -horizontal', 'item -normal','item -small','item -big']
        if(type == 'number'){
          return r
        }else{
          return arr[Math.floor(Math.random() * (arr.length-1 - 0 + 1) + 0)]
        }
      }
      getData()
      class Example {
        constructor(options) {
          this.root = options.root;
          this.init();
          setTimeout(this.showImages.bind(this), 1000);
        }
        init() {
          this.scroll = new LocomotiveScroll({
              el: this.root,
              direction: 'horizontal',
              smooth: true,
              lerp: 0.05,
              tablet: { smooth: true },
              smartphone: { smooth: true }
          });
          this.images = this.root.querySelectorAll('.image');

          [].forEach.call(this.images, (image) => {
            image.addEventListener('click', () => {
              image.classList.add('-clicked');
              // 点击后操作,自行修改
              this.hideImages();
            });
          });
        }
        showImages() {
          [].forEach.call(this.images, (image) => {
            image.classList.remove('-clicked');
            image.classList.add('-active');
          });
        }
        hideImages() {
          [].forEach.call(this.images, (image) => {
              image.classList.remove('-active');
          });

          setTimeout(this.showImages.bind(this), 2000);
        }
      }
      window.addEventListener('DOMContentLoaded', (event) => {
        const example = new Example({
          root: document.querySelector('.scroll-animations-example')
        });
      });
    </script> 
	</body>
</html>

鼠标移动小挂件

这个跟屁虫和小狗的区别还是很大的,小狗有动画,可交互,这个单纯只是个跟屁虫。元素可随意更换,可以是文字,可以是表情,可以是Icon等等。这是我跟chatGPT软磨硬泡才搞出来的,他笨的我想揍人。

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>people</title>
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,minimal-ui">
		<style>
			html, body { height: 100%; }
      body { overflow: hidden; }
      .over-flash {
        height: 100vh;
        width: 100vw;
        background-color: #f3d2d2;
        position: fixed; 
        pointer-events: none;
        top: 0;
        left: 0;
      }
      #plane{
        color: #fff;
        font-size: 30px;
        /* 绝对定位 */
        position: absolute;
        display: flex;
        justify-content: center;
        align-items: center;
      }
		</style>
	</head>
	<body>
    <!-- 就是这里,这个樱桃可以改成任何元素 -->
    <div class="over-flash"> <div id="plane">🍒</div> </div>
		<script>
        let plane = document.getElementById('plane');
        let deg = 0, ex=0, ey = 0, vx = 0, vy = 0, count = 0;
        window.addEventListener('mousemove',(e) => {
          ex = e.x - plane.offsetLeft - plane.clientWidth / 2;
          ey = e.y - plane.offsetTop - plane.clientHeight / 2;
          deg = 360 * Math.atan(ey / ex) / (2*Math.PI) + 45;
          if (ex < 0) {
              deg += 180;
          }
          count = 0;
        })
        function draw(){
          plane.style.transform = 'rotate(' + deg + 'deg)';
          if ( count < 100 ) {
              vx += ex / 100;
              vy += ey / 100;
          }
          plane.style.left = vx + 'px';
          plane.style.top = vy + 'px';
          count++;
        }
        setInterval(draw, 1);
      </script>
	</body>
</html>