前端图像处理(二)
目录
一、上传
1.1、文件夹上传以及进度追踪
1.2、拖拽上传
1.3、图片裁剪上传原理
二、图片布局
2.1、渐进式图片
2.2、图片九宫格
2.3、轮播图(Js)
2.3.1、3D动画轮播图
2.3.2、旋转切换的轮播图
2.4、卡片移入+翻转效果
2.5、环绕式照片墙
一、上传
1.1、文件夹上传以及进度追踪
效果展示:
交互:如何选择多个文件?如何选择文件夹?如何拖拽文件和文件夹?
网络:如何实现多文件上传?如何实现进度追踪?如何实现取消上传?
- 上传逻辑:
uploadFile
函数创建一个XMLHttpRequest
对象,设置请求方法和 URL,并打开请求。它使用FormData
来包含要上传的文件。- 进度追踪:通过监听
xhr.upload.onprogress
事件,可以追踪上传进度,上传进度的计算依赖于event.lengthComputable
属性。- 取消上传:
uploadFile
函数返回一个取消函数,当调用这个函数时,它会调用xhr.abort()
来取消上传。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.container {
border: 1px solid black;
width: 50%;
height: 300px;
}
</style>
</head>
<body>
<div class="container"></div>
<script>
const div = document.querySelector(".container");
div.ondragenter = (e) => {
e.preventDefault();
};
div.ondragover = (e) => {
e.preventDefault();
};
div.ondrop = (e) => {
e.preventDefault();
const items = e.dataTransfer.items;
for (const item of items) {
// item 是 DataTransferItem 对象
const entry = item.webkitGetAsEntry(); // 拿到 FileEntry 对象
processEntry(entry);
}
};
// 使用递归处理拖放的文件和文件夹
function processEntry(entry) {
if (entry.isFile) {
// 处理文件:拿到 File 文件
entry.file((file) => {
uploadFile(file);
});
} else if (entry.isDirectory) {
// 处理文件夹:文件夹中的文件 File 文件对象
const reader = entry.createReader();
reader.readEntries((entries) => {
entries.forEach((entry) => {
processEntry(entry); // 递归处理每个条目
});
});
}
}
function uploadFile(file) {
const formData = new FormData();
formData.append("file", file);
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://jsonplaceholder.typicode.com/posts/", true);
// 上传进度追踪
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Upload progress:${percentComplete}%`); //fetch无法追踪请求进度
}
};
// 上传完成
xhr.onload = () => {
if (xhr.status === 200) {
console.log("Upload completed");
} else {
console.log("Upload error");
}
};
// 上传错误
xhr.onerror = () => {
console.log("Upload error");
};
// 发送请求
xhr.send(formData);
// 返回取消函数
return () => {
xhr.abort(); //取消上传
};
}
</script>
</body>
</html>
1.2、拖拽上传
画一个盒子,盒子里放个上传的input,主要就是看在外界图片直接拖拽进盒子时,要如何处理。
doms.ondragenter = (e) => {
e.preventDefault();
e.target.classList.add("draging");
};
doms.ondragover = (e) => {
e.preventDefault();
};
doms.ondrop = (e) => {
e.preventDefault();
e.target.classList.remove("draging");
const files=e.dataTransfer.files
const types=e.dataTransfer.types
console.log("FileList数据",files,"文件类型",types)
};
1.3、图片裁剪上传原理
<body>
<input type="file" />
<img class="preview" />
<button>上传裁剪后的结果</button>
<script>
const inp = document.querySelector("input");
const img = document.querySelector("img");
inp.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
};
reader.readAsDataURL(file);
};
// 假设已经拿到剪裁后的结果
function uploadResult({ cutWidth, cutHeight, cutX, cutY }) {
const cvs = document.createElement("canvas");
const ctx = cvs.getContext("2d");
cvs.width = 200;
cvs.height = 200;
ctx.drawImage(
img,
cutX,
cutY,
cutWidth,
cutHeight,
0,
0,
cvs.width,
cvs.height
);
document.body.appendChild(cvs);
}
const btn = document.querySelector("button");
btn.onclick = () => {
uploadResult({
cutWidth: 200,
cutHeight: 200,
cutX: 100,
cutY: 100,
});
};
</script>
</body>
二、图片布局
2.1、渐进式图片
【场景】加载大图:一开始是模糊的图片,慢慢变的清晰
UI可以给出一张jpg【支持多帧】:第一帧模糊小图,第二帧高清大图,给不了就自己写:
这个组件在使用时直接传入图片即可!
<template>
<div class="progressive">
<img :src="placeholder" class="img placeholder" />
<img :src="origin" class="img origin" @load="handleLoaded" />
</div>
</template>
<script>
export default {
// placeholder:模糊小图 origin:原始大图
props: ['placeholder', 'origin'],
methods: {
handleLoaded(e) {
e.target.parentElement.classList.add('loaded')
}
}
}
</script>
<style scoped>
.progressive {
position: relative;
}
.img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
transition: 0.6s;
}
.origin {
opacity: 0;
position: absolute;
left: 0;
top: 0;
/* 给 .origin 图像应用一个模糊效果 */
filter: blur(10px);
}
.loaded .origin {
opacity: 1;
/* 移除模糊效果,使图像变得清晰 */
filter: blur(0);
}
</style>
2.2、图片九宫格
<style>
/* 每个图片都是500 * 500 的 */
.img-box {
width: 500px;
border: 1px solid red;
}
.img-container {
height: 500px;
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.img-item {
box-shadow: inset 0 0 0 5px #fff;
transition: 0.5s;
background-size: 500px 500px;
background-image: url(./001.png);
background-position: var(--bgx, 0) var(--bgY, 0);
transform: translate(var(--disX, 0), var(--disY, 0));
}
.img-container:hover .img-item {
box-shadow: inset 0 0 0 0 #fff;
}
</style>
<body>
<div class="img-box">
<div class="img-container">
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
<div class="img-item"></div>
</div>
</div>
<script>
const items = document.querySelectorAll(".img-item");
for (let i = 0; i < items.length; i++) {
const r = Math.floor(i / 3); // 行索引
const c = i % 3; // 列索引
const bgX = -c * (500 / 3) + "px"; // 背景X位置
const bgY = -r * (500 / 3) + "px"; // 背景Y位置
// 位移限制在容器内
const disX = c; // 水平位移
const disY = r; // 垂直位移
items[i].style.setProperty("--bgx", bgX);
items[i].style.setProperty("--bgY", bgY);
items[i].style.setProperty("--disX", disX + "px");
items[i].style.setProperty("--disY", disY + "px");
}
</script>
</body>
2.3、轮播图(Js)
2.3.1、3D动画轮播图
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.carousel-box {
width: 65%;
height: 80vh;
background-color: black;
position: relative;
perspective: 1000px; /* 添加透视效果 */
transform-style: preserve-3d; /* 保持3D变换 */
}
.carousel-box span {
font-size: 40px;
color: aqua;
cursor: pointer;
}
.carousel-item {
position: absolute;
left: 30%;
top: 30%;
transform: translate(-50%, -50%) translateZ(0); /* 使用translateZ添加3D效果 */
transition: transform 0.1s;
}
.prev-btn {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 99;
}
.next-btn {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 99;
}
</style>
<body>
<div class="carousel-box">
<span class="prev-btn">《</span>
<span class="next-btn">》</span>
<img src="https://picsum.photos/id/41/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/42/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/43/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/44/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/45/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/46/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/47/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/48/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/49/400/300/" class="carousel-item" />
<img src="https://picsum.photos/id/50/400/300/" class="carousel-item" />
</div>
<script>
const items = document.querySelectorAll(".carousel-item");
let index = 3; //当前显示的图片索引
function layout() {
const offsetStep = 100; //每两张图片之间的偏移量
const scaleStep = 0.5; //每两张图片之间的缩放比例差
const opacityStep = 0.5; //每两张图片之间的透明度差
for (let i = 0; i < items.length; i++) {
const item = items[i];
const dis = Math.abs(i - index);
const sign = Math.sign(i - index);
let xOffset = (i - index) * offsetStep;
if (i != index) {
xOffset = xOffset + 100 * sign;
}
const scale = scaleStep ** dis;
const rotateY = 45 * -sign; // 增加旋转角度
item.style.transform = `translateX(${xOffset}px) scale(${scale}) rotateY(${rotateY}deg)`;
const opacity = opacityStep ** dis;
item.style.opacity = opacity;
const zIndex = items.length - dis; //层级
item.style.zIndex = zIndex;
}
}
layout();
const prev = document.querySelector(".prev-btn");
const next = document.querySelector(".next-btn");
prev.addEventListener("click", function () {
index--;
if (index < 0) {
index = items.length - 1;
}
layout();
});
next.addEventListener("click", function () {
index++;
if (index > items.length - 1) {
index = 0;
}
layout();
});
items.forEach((item, i) => {
item.addEventListener("click", function (event) {
event.preventDefault(); // 阻止默认行为,即不触发下载
index = i;
layout();
});
});
</script>
</body>
2.3.2、旋转切换的轮播图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="container">
<div class="inner">
<img src="https://picsum.photos/id/51/600" alt="">
<img src="https://picsum.photos/id/52/600" alt="">
<img src="https://picsum.photos/id/53/600" alt="">
<img src="https://picsum.photos/id/54/600" alt="">
<img src="https://picsum.photos/id/55/600" alt="">
<img src="https://picsum.photos/id/56/600" alt="">
</div>
</div>
</body>
</html>
styles.scss文件:
@use "sass:math";
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: black;
}
$size: 200px;
$n: 6;
$pDeg: 360deg / $n;
$r: $size/2;
$R: $r/math.sin($pDeg/2);
$innerSize: $R * 2;
.container {
width: $size;
height: $size;
outline: 5px solid #fff;
border-radius: 50%;
margin: 50px auto;
display: flex;
justify-content: center;
overflow: hidden;
}
.inner {
width: $innerSize;
height: $innerSize;
border-radius: 50%;
// background-color: #f40;//基于红色圈在转:父元素去掉overflow: hidden可以查看原理
flex-shrink: 0;
margin-top: $r;
position: relative;
img {
width: $size;
height: $size;
border-radius: 50%;
position: absolute;
left: 50%;
margin-left: -$size/2;
top: -$r;
transform-origin: center #{$r + $R};
@for $i from 1 through $n {
&:nth-child(#{$i}) {
transform: rotate($pDeg * ($i - 1));
}
}
}
}
$u: 1 / $n * 100%;
$stopPercent: 0.6 * $u;
@keyframes moving {
@for $i from 1 through $n {
$p: $u * $i;
$deg: $pDeg * $i;
#{$p - $stopPercent},
#{$p} {
transform: rotate(-$deg);
}
}
}
.inner {
animation: moving 10s infinite;
}
2.4、卡片移入+翻转效果
移入:从平面变成3D翻起来效果
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
.card {
width: 428px;
height: 475px;
margin: 0 50px;
transform: translateY(-50%);
position: relative;
}
.card img {
width: 100%;
position: absolute;
transition: 0.5s;
}
.card .cover {
z-index: 1;
}
.card:hover .cover {
box-shadow: 0 35px 35px -8px rgb(0, 0, 0, 0.75);
transform: perspective(500px) rotateX(25deg);
}
.card .title {
z-index: 3;
bottom: 0;
right: 0;
}
.card:hover .title {
transform: perspective(500px) translate3d(0, -25px, 50px);
}
.card .hero {
z-index: 2;
opacity: 0;
}
.card:hover .hero {
opacity: 1;
transform: perspective(500px) translate3d(0, -50px, 50px);
}
</style>
<body>
<div class="card">
<img src="./images/ping.png" alt="" class="cover" />
<img src="./images/wenzi.png" alt="" class="title" />
<img src="./images/liqilai.png" alt="" class="hero" />
</div>
</body>
翻转:一张卡片展示正面,翻转后看到另一面
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.card {
perspective: 500px; /* 给父元素设置透视效果 */
transform-style: preserve-3d; /* 保证子元素的 3D 变换效果生效 */
width: 200px; /* 设置宽度和高度,确保翻转效果可以看到 */
height: 300px;
position: relative; /* 确保子元素的定位 */
}
.card .face,
.card .back {
position: absolute; /* 确保两张图片在同一位置 */
top: 0;
left: 0;
width: 100%;
height: 100%;
backface-visibility: hidden; /* 隐藏背面的图片 */
transition: transform 0.6s; /* 加入过渡效果 */
}
.card .face {
transform: rotateY(0deg); /* 正面显示 */
}
.card .back {
transform: rotateY(180deg); /* 背面显示 */
}
.card:hover .face {
transform: rotateY(-180deg); /* 当悬停时,翻转正面 */
}
.card:hover .back {
transform: rotateY(0deg); /* 背面正向显示 */
}
</style>
<body>
<div class="card">
<img src="./images/zheng.png" alt="" class="face" />
<img src="./images/bei.jpg" alt="" class="back" />
</div>
</body>
2.5、环绕式照片墙
使用sass
下载:npm install -g sass
编译:sass styles.scss styles.css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="ring">
<img src="https://picsum.photos/id/41/400/600/" alt="" />
<img src="https://picsum.photos/id/42/400/600/" alt="" />
<img src="https://picsum.photos/id/43/400/600/" alt="" />
<img src="https://picsum.photos/id/44/400/600/" alt="" />
<img src="https://picsum.photos/id/45/400/600/" alt="" />
<img src="https://picsum.photos/id/46/400/600/" alt="" />
<img src="https://picsum.photos/id/47/400/600/" alt="" />
<img src="https://picsum.photos/id/48/400/600/" alt="" />
<img src="https://picsum.photos/id/49/400/600/" alt="" />
<img src="https://picsum.photos/id/50/400/600/" alt="" />
</div>
<script>
const ring = document.querySelector(".ring");
let angle = 0;
// 创建定时器变量
let intervalId = null;
function rotateRing() {
intervalId = setInterval(() => {
// 每次增加10度
angle += 10;
// 如果角度达到或超过360度,重置为0
if (angle >= 360) {
angle = 0;
}
ring.style.transform = `rotateY(${angle}deg)`;
}, 100); // 每100毫秒执行一次
}
// 开始旋转
rotateRing();
// 监听鼠标点击事件,停止旋转
document.addEventListener("click", () => {
clearInterval(intervalId); // 停止定时器
});
</script>
</body>
</html>
// ======================styles.scss文件===================
@use "sass:math";
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
overflow: hidden;
}
body {
background-color: black;
perspective: 2000px;
}
$imgWidth: 300px;
$imgHeight: 400px;
$n: 10; // 总的图片数量
$pDeg: 360deg / $n; // 每个图片的角度间隔
$r: 500px; // 半径
.ring {
width: 100vw;
height: 100vh;
position: relative;
transform-style: preserve-3d;
img {
position: absolute;
width: $imgWidth;
height: $imgHeight;
left: 50%;
top: 50%;
margin-left: calc(-1 * #{$imgWidth} / 2);
margin-top: calc(-1 * #{$imgHeight} / 2);
backface-visibility: hidden;
opacity: 0.5;
transition: .5s;
&:hover {
opacity: 1;
}
// 使用循环将图片分布到圆形轨迹上
@for $i from 1 through $n {
&:nth-child(#{$i}) {
$deg: $pDeg * ($i - 1); // 当前图片的角度
$x: math.sin($deg) * $r; // 计算 X 坐标
$z: math.cos($deg) * $r; // 计算 Z 坐标
transform: translate3d($x, 0, $z) rotateY(180deg + $deg);
}
}
}
}
// 已知:
// sin(θ)= 对边/斜边
// cos(θ)= 邻边/斜边
备注:旋转切换的轮播图和环绕式照片墙案例都用scss进行了计算,编译:sass styles.scss styles.css查看css代码,明显减少了代码,后面将分享scss的相关属性和方法。