前端炫酷动画--图片(一)
目录
一、四角线框的跟随移动
二、元素倒影(-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 15px
linear-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>