目录
一、四角线框的跟随移动
二、元素倒影(-webkit-box-reflect)
三、模特换装(mask+blend)
四、元素平滑上升
五、无限视差滚动
六、判断鼠标进入方向(轮播方向)
七、环形旋转效果
八、黑白小球交替旋转
九、hover时圆形放大
十、画一棵随机树(canvas)
十一、代码雨效果(canvas)
一、四角线框的跟随移动
<style>body {background: black;}.container {width: 400px;height: 40vh;position: relative;display: grid; /* 使用 Grid 布局 */grid-template-columns: repeat(2, 1fr); /* 设置两列 */grid-gap: 20px; /* 设置网格项之间的间距 */justify-items: center;align-items: center;margin: 20px;}.pointer {position: absolute;--l: 30px; /* 长度 */--g: 15px; /* 间隔 */--t: 3px; /* 粗细 */--s: 394px; /* 框住的大小 */--x: 0px;--y: 0px;width: calc(var(--s) + var(--g) * 2);height: calc(var(--s) + var(--g) * 2);border: var(--t) solid #fff;left: calc(var(--x) - var(--g));top: calc(var(--y) - var(--g));transition: 0.5s;/* 圆锥渐变(conic-gradient)作为遮罩效果 */-webkit-mask: conic-gradient(at var(--l) var(--l),transparent 75%,red 75%)0 0 / calc(100% - var(--l)) calc(100% - var(--l));}</style><body><div class="container"><div class="pointer"></div><div class="item"><img src="https://picsum.photos/id/371/400/400" alt="" /></div><div class="item"><img src="https://picsum.photos/id/372/400/400" alt="" /></div><div class="item"><img src="https://picsum.photos/id/374/400/400" alt="" /></div><div class="item"><img src="https://picsum.photos/id/376/400/400" alt="" /></div></div><script>const imgs = document.querySelectorAll(".container img");const pointer = document.querySelector(".pointer");for (const img of imgs) {img.onmouseenter = () => {pointer.style.setProperty("--x", img.offsetLeft + "px");pointer.style.setProperty("--y", img.offsetTop + "px");pointer.style.setProperty("--s", img.offsetWidth + "px");};}</script></body>
二、元素倒影(-webkit-box-reflect)
.card {box-shadow: 0 0 8px #fff;width: 200px;-webkit-box-reflect: below 15pxlinear-gradient(transparent, transparent, #0005);}
三、模特换装(mask+blend)
前提:准备一张白色完整图片和要换装的衣服块图片,后期再混合
<style>.card {width: 300px;height: 500px;position: relative;}.source {display: block;width: 100%;height: 100%;object-fit: cover;}.skirt {position: absolute;inset: 0; /*等同于 left:0;top:0;bottom: 0;right: 0; */background:red;--mask:url(./partImg.png) 50% 50% / cover;/* 将用蒙版将衣服块“染成”背景色 */mask:var(--mask);-webkit-mask:var(--mask);/* 混合衣服块和背景色 */mix-blend-mode: multiply;}</style><body><div class="card"><img src="./whiteImg.png" alt="" class="source" /><div class="skirt"></div></div></body>
四、元素平滑上升
// useSlideIn.js
const DISTANCE = 150;
const DURATION = 500;
const map = new WeakMap();
const ob = new IntersectionObserver((entries) => {for (const entry of entries) {if (entry.isIntersecting) {// 该元素和视口相交 播放该元素的动画const animation = map.get(entry.target);if (animation) {animation.play();ob.unobserve(entry.target); // 播放一次,取消观察}}}
});
function isBelowViewport(el) {const rect = el.getBoundingClientRect();return rect.top - DISTANCE > window.innerHeight;
}
export default {mounted(el) {if (!isBelowViewport(el)) {return;}const animation = el.animate([{transform: `translateY(${DISTANCE}px)`,opacity: 0.5},{transform: 'translateY(0)',opacity: 1}],{duration: DURATION,easing: 'ease-in-out',fill: 'forwards'});animation.pause();ob.observe(el);map.set(el, animation);},unmounted(el) {ob.unobserve(el);}
};
<template><div><div v-slide-in class="item" v-for="n in 10" :key="n">{{ n }}BOX</div></div>
</template>
<script>
import slideIn from './useSlideIn';
export default {directives: {'slide-in': slideIn},
};
</script>
五、无限视差滚动
<style>.scroll-container {display: flex;overflow: hidden;position: relative;height: 400px; /* 设置容器高度 */}.item {position: absolute;width: 100%;height: 100%;transition: transform 0.5s ease;}.item img {width: 100%;height: 100%;object-fit: cover;}.scroll-down .cur {transform: translateY(100%);}.scroll-up .cur {transform: translateY(-100%);}</style>
<body><div class="scroll-container"></div><script>const imgs = ["https://picsum.photos/id/376/800/800","https://picsum.photos/id/372/800/800","https://picsum.photos/id/373/800/800","https://picsum.photos/id/374/800/800","https://picsum.photos/id/375/800/800"];const container = document.querySelector(".scroll-container");let curIndex = 0;function getPrevIndex() {return curIndex === 0 ? imgs.length - 1 : curIndex - 1;}function getNextIndex() {return curIndex === imgs.length - 1 ? 0 : curIndex + 1;}function createElement(i) {const div = document.createElement("div");div.className = "item";const img = document.createElement("img");img.src = imgs[i];div.appendChild(img);container.appendChild(div);return div;}function resetElements() {container.innerHTML = "";const prevIndex = getPrevIndex();const nextIndex = getNextIndex();createElement(prevIndex).classList.add("prev");const curItem = createElement(curIndex);curItem.classList.add("cur");createElement(nextIndex).classList.add("next");}resetElements();let isAnimation = false;window.addEventListener("wheel", (e) => {if (!e.deltaY || isAnimation) {return;}isAnimation = true;if (e.deltaY > 0) {curIndex = getNextIndex();container.classList.add("scroll-down");} else {curIndex = getPrevIndex();container.classList.add("scroll-up");}});container.addEventListener("transitionend", () => {container.classList.remove("scroll-down");container.classList.remove("scroll-up");isAnimation = false;resetElements();});</script>
</body>
六、判断鼠标进入方向(轮播方向)
<script>const container = document.querySelector(".container");const rect = container.getBoundingClientRect();const theta = Math.atan2(rect.height, rect.width);container.addEventListener("mouseenter", (e) => {const x = e.offsetX - rect.width / 2;const y = rect.height / 2 - e.offsetY;const d = Math.atan2(y, x);if (d < theta && d >= -theta) {container.classList.add("right");} else if (d >= theta && d < Math.PI - theta) {container.classList.add("top");} else if (d >= Math.PI - theta || d < -(Math.PI - theta)) {container.classList.add("left");} else {container.classList.add("bottom");}});container.addEventListener("mouseleave", () => {container.className = "container";});</script>
七、环形旋转效果
$size: 300px;
$imgSize: 80px;
.container {width: $size;height: $size;outline: 1px solid #000;margin: 0 auto;position: relative;margin-top: 60px;display: flex;justify-content: center;align-items: start;border-radius: 50%;animation: rotation 20s linear infinite;@keyframes rotation {to {transform: rotate(calc(360deg - var(--initDeg, 0deg)));}}.item {width: $imgSize;height: $imgSize;position: absolute;margin-top: -40px;img {width: 100%;height: 100%;object-fit: cover;border-radius: 50%;}}
}
$n: 5;
$pDeg: 360deg / $n;
.item {transform-origin: center $size / 2 + $imgSize / 2;@for $i from 1 through $n {$deg: $pDeg * ($i - 1);&:nth-child(#{$i}) {transform: rotate($deg);img {--initDeg:#{$deg};transform: rotate(-$deg); //将歪斜的图片矫正animation: rotation 20s linear infinite reverse;}}}
}
八、黑白小球交替旋转
<div class="loading"><!-- 快捷键:div.dot*36 --></div>
body {background: #66c7f4;
}
$ballSize: 10px; //小球尺寸
$containerSize: 150px; //容器尺寸
$n: 36;
$pDeg: 360deg / $n;
$d:2s;
.loading {width: $containerSize;height: $containerSize;margin: 50px auto;position: relative;border-radius: 50%;// outline: 1px solid #fff;
}
.dot {position: absolute;left: 50%;top: 0;width: $ballSize;height: $ballSize;margin-left: -$ballSize / 2;margin-top: -$ballSize / 2;perspective: 70px;// background: #f40;transform-origin: center $containerSize / 2 + $ballSize / 2;perspective: 70px;transform-style: preserve-3d;@for $i from 1 through $n {&:nth-child(#{$i}) {transform: rotate($pDeg * ($i - 1));&::before,&::after {animation-delay: -$d / $n * ($i - 1) * 6;}}&::before,&::after {content: "";position: absolute;width: 100%;height: 100%;border-radius: 50%;}&::before {background: #000;top: -100%;animation: rotation-black $d infinite;@keyframes rotation-black {0% {animation-timing-function: ease-in;}25% {transform: translate3d(0, 100%, $ballSize);animation-timing-function: ease-out;}50% {transform: translate3d(0, 200%, 0);animation-timing-function: ease-in;}75% {transform: translate3d(0, 100%, -$ballSize);animation-timing-function: ease-out;}}}&::after {background: #fff;top: 100%;animation: rotation-white $d infinite;@keyframes rotation-white {0% {animation-timing-function: ease-in;}25% {transform: translate3d(0, -100%, -$ballSize);animation-timing-function: ease-out;}50% {transform: translate3d(0, -200%, 0);animation-timing-function: ease-in;}75% {transform: translate3d(0, -100%, $ballSize);animation-timing-function: ease-out;}}}}
}
九、hover时圆形放大
<style>.avatar {width: 200px;height: 200px;border-radius: 50%;background: url("./qiang.jpg");cursor: pointer;position: relative;}.avatar::before,.avatar::after {content: "";position: absolute;inset: 0;border-radius: 50%;}.avatar::before {background: rgba(0, 0, 0, 0.5);}.avatar::after {background: inherit; /* 继承自父元素 */clip-path: circle(0% at 50% 50%);transition: .3s;}.avatar:hover::after {clip-path: circle(50% at 50% 50%);}</style><body><div class="avatar"></div></body>
十、画一棵随机树(canvas)
<body><canvas id="bg"></canvas><script>const cvs = document.getElementById("bg");const ctx = cvs.getContext("2d");cvs.width = window.innerWidth;cvs.height = window.innerHeight;// 更改坐标原点ctx.translate(cvs.width / 2, cvs.height);ctx.scale(1, -1);// 画树干drawBranch([0, 0], 200, 30, 90);function drawBranch(v0, length, thick, dir) {if (thick < 10 && Math.random() < 0.3) {return;}if (thick < 2) {ctx.beginPath();ctx.arc(...v0, 10, 0, 2 * Math.PI);ctx.fillStyle = "red";ctx.fill();return;}ctx.beginPath();ctx.moveTo(...v0);const v1 = [v0[0] + length * Math.cos((dir * Math.PI) / 180),v0[1] + length * Math.sin((dir * Math.PI) / 180),];ctx.lineTo(...v1);ctx.lineWidth = thick;ctx.fillStyle = "#333";ctx.lineCap = "round";ctx.stroke();// 递归调用画左右两边的树枝drawBranch(v1, length * 0.8, thick * 0.8, dir + Math.random() * 30);drawBranch(v1, length * 0.8, thick * 0.8, dir - Math.random() * 30);}</script></body>
十一、代码雨效果(canvas)
<body><canvas id="bg"></canvas><script>const cvs = document.getElementById("bg");const width = window.innerWidth * devicePixelRatio,height = window.innerHeight * devicePixelRatio;// 设置canvas尺寸为窗口尺寸cvs.width = width;cvs.height = height;const ctx = cvs.getContext("2d");const fontSize = 20 * devicePixelRatio;const columnWidth = fontSize; //列宽const columnCount = Math.floor(width / columnWidth); //列的数量const nextChar = new Array(columnCount).fill(0); //每列下一个文字是第几个文字// 获取随机颜色 (HEX 格式)function getRandomColor() {const r = Math.floor(Math.random() * 256);const g = Math.floor(Math.random() * 256);const b = Math.floor(Math.random() * 256);// 将每个颜色分量转换为两位的十六进制格式,并拼接成 HEX 颜色值return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase()}`;}// 获取随机字符function getRandomChar() {const chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";const randomIndex = Math.floor(Math.random() * chars.length);return chars[randomIndex];}function draw() {ctx.fillStyle = "rgba(0,0,0,0.1)";ctx.fillRect(0, 0, width, height);for (let i = 0; i < columnCount; i++) {const char = getRandomChar();ctx.fillStyle ='green';//赋值为getRandomColor()就是随机彩色ctx.font = `${fontSize}px "Roboto Mono"`;const x = columnWidth * i;const index = nextChar[i];const y = (index + 1) * fontSize;ctx.fillText(char, x, y);if (y > height && Math.random() > 0.99) {nextChar[i] = 0;} else {nextChar[i]++;}}}draw();setInterval(draw, 40);</script></body>