效果不完整 后期会进一步优化动画效果就是感觉好玩 如果小伙伴有自己的项目 感觉有兴趣可以玩玩
效果图演示
VUE + Element-Ui + Canvas 主要是这些 接下来说一下实现的拆分效果
这个比较重要 主要是当成背景图进行使用 先说一下代码使用
这两张图片放到项目中进行备用
// 这里是图片地址
import bridge from "../../images/bridge.png";
class WaterRipple {
constructor(props) {
this.init(props);
}
init(props) {
this.boxRef = props.boxRef;
this.canvas = props.canvas;
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
this.width = this.canvas.width;
this.height = this.canvas.height;
this.max_amplitude = 1024;
this.amplitude = props.amplitude || 512;
this.background = props.background;
this.load = false;
this.img_data = null;
this.new_img_data = null;
this.animation_idx = null;
this.mousemove_interval = props.mousemove_interval || 50;
this.animation_interval = props.animation_interval || 20;
this.ripple_radius = props.ripple_radius || 3;
this.bridge = new Image();
this.bridge.src = bridge;
if (props.resize) {
this.setResize();
}
}
drawBackground() {
if (!this.background.complete) return;
this.ctx.drawImage(this.background, 0, 0, this.width, this.height);
this.img_data = this.ctx.getImageData(0, 0, this.width, this.height);
this.new_img_data = this.ctx.getImageData(0, 0, this.width, this.height);
this.buffer_1 = new Array(this.width)
.fill(0)
.map(() => new Array(this.height).fill(0));
this.buffer_2 = new Array(this.width)
.fill(0)
.map(() => new Array(this.height).fill(0));
this.load = true;
}
render() {
if (!this.load) {
this.drawBackground();
return;
}
const w = this.width;
const h = this.height;
const half_w = w >> 1;
const half_h = h >> 1;
const len = this.new_img_data.data.length;
for (let i = 0; i < len; i += 4) {
const buffer_idx = i >> 2;
const x = buffer_idx % w;
const y = (buffer_idx - x) / w;
const left = Math.max(x - 1, 0);
const right = Math.min(x + 1, w - 1);
const top = Math.min(y + 1, h - 1);
const bottom = Math.max(y - 1, 0);
let next_amplitude =
this.buffer_1[x][top] +
this.buffer_1[x][bottom] +
this.buffer_1[right][y] +
this.buffer_1[left][y];
next_amplitude >>= 1;
next_amplitude -= this.buffer_2[x][y];
next_amplitude -= next_amplitude >> 5;
this.buffer_2[x][y] = next_amplitude;
const ratio = (this.max_amplitude - next_amplitude) / this.max_amplitude;
if (next_amplitude !== 0) {
let offset_x = (((x - half_w) * ratio) << 0) + half_w;
let offset_y = (((y - half_h) * ratio) << 0) + half_h;
offset_x = Math.min(offset_x, w - 1);
offset_x = Math.max(offset_x, 0);
offset_y = Math.max(offset_y, 0);
offset_y = Math.min(offset_y, h - 1);
const img_data_idx = (offset_y * w + offset_x) << 2;
const r = this.img_data.data[img_data_idx];
const g = this.img_data.data[img_data_idx + 1];
const b = this.img_data.data[img_data_idx + 2];
this.new_img_data.data[i] = r;
this.new_img_data.data[i + 1] = g;
this.new_img_data.data[i + 2] = b;
}
}
this.ctx.putImageData(this.new_img_data, 0, 0);
this.ctx.drawImage(
this.bridge,
0,
this.height * 0.35,
this.width,
this.height * 0.65
);
const temp_data = this.buffer_2;
this.buffer_2 = this.buffer_1;
this.buffer_1 = temp_data;
}
animate() {
let start = new Date().getTime();
const update = () => {
const end = new Date().getTime();
if (end - start > this.animation_interval) {
this.render();
start = end;
}
this.animation_idx = requestAnimationFrame(update);
};
update();
}
throttle(func, time) {
let start = new Date().getTime();
return function () {
const end = new Date().getTime();
if (end - start >= time) {
start = end;
func.apply(this, arguments);
}
};
}
ripple(x, y) {
if (!this.load) return;
const left = Math.max(x - this.ripple_radius, 0);
const right = Math.min(x + this.ripple_radius, this.buffer_1.length - 1);
const top = Math.max(y - this.ripple_radius, 0);
const bottom = Math.min(
y + this.ripple_radius,
this.buffer_1[0].length - 1
);
for (let i = left; i < right; i++) {
for (let j = top; j < bottom; j++) {
this.buffer_1[i][j] = this.amplitude;
}
}
}
addMousemove() {
this.canvas.addEventListener(
"mousemove",
this.throttle((e) => {
const { top, left } = this.canvas.getBoundingClientRect();
this.ripple(Math.floor(e.clientX - left), Math.floor(e.clientY - top));
}, this.mousemove_interval)
);
}
setResize() {
window.addEventListener("resize", () => {
const { offsetWidth, offsetHeight } = this.boxRef.current;
this.canvas.width = offsetWidth;
this.canvas.height = offsetHeight;
this.width = this.canvas.width;
this.height = this.canvas.height;
this.load = false;
});
}
stop() {
cancelAnimationFrame(this.animation_idx);
}
resume() {
this.animate();
}
}
export default WaterRipple;
<template>
<div
ref="boxRef"
style="
boxsizing: border-box;
width: 100%;
height: 100vh;
position: relative;
"
>
<canvas ref="canvasRef" style="z-index: 1; position: absolute"></canvas>
<el-card class="login-card" v-if="cardType == 1">
<div class="login-title">
<p data-text="变成派大星后台管理">变成派大星后台管理</p>
</div>
<!-- 登录表单 -->
<el-form
status-icon
:model="loginForm"
:rules="rules"
ref="ruleForm"
class="login-form"
>
<!-- 用户名输入框 -->
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
prefix-icon="el-icon-aliyonghu"
placeholder="用户名"
@keyup.enter.native="login"
/>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
prefix-icon="el-icon-alimima2"
show-password
placeholder="密码"
@keyup.enter.native="login"
/>
</el-form-item>
</el-form>
<!-- 登录按钮 -->
<el-footer class="footer-container">
<el-button
class="login-button"
type="success"
round
@click="login"
>登录账号</el-button
>
<el-button
class="register-button"
type="info"
@click="cardType = 2"
round
>注册账号</el-button
>
</el-footer>
</el-card>
<!-- 注册界面-->
<el-card class="login-card" v-if="cardType == 2">
<div class="login-title">
<p data-text="变成派大星后台管理">变成派大星后台管理</p>
</div>
<!-- 注册表单 -->
<el-form
status-icon
:model="register"
:rules="rules"
ref="ruleForm"
class="login-form"
>
<!-- 用户名输入框 -->
<el-form-item prop="username">
<el-input
v-model="register.username"
prefix-icon="el-icon-aliyonghu"
placeholder="用户名"
@keyup.enter.native="login"
/>
</el-form-item>
<!-- 邮箱 -->
<el-form-item prop="email">
<el-input
v-model="register.email"
prefix-icon="el-icon-alialimailaliyouxiang"
placeholder="邮箱"
@keyup.enter.native="email"
/>
</el-form-item>
<!-- 验证码 -->
<el-form-item prop="验证码" class="form-item-flex">
<el-input
style="width: 75%"
v-model="register.code"
prefix-icon="el-icon-aliyanzhengma"
placeholder="验证码"
:controls="false"
/>
<el-button
@click="sendCode"
style="width: 25%; border-radius: 15px; margin-top: 0"
type="success"
>{{ emailSendText }}</el-button
>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item prop="password">
<el-input
v-model="register.password"
prefix-icon="el-icon-alimima2"
show-password
placeholder="密码"
@keyup.enter.native="login"
/>
</el-form-item>
<!-- 确认密码 -->
<el-form-item prop="confirmPassword">
<el-input
v-model="register.confirmPassword"
prefix-icon="el-icon-alimima"
show-password
placeholder="确认密码"
@keyup.enter.native="login"
/>
</el-form-item>
</el-form>
<el-footer class="footer-container">
<el-button
class="register-button"
type="success"
round
@click="registerUser"
>注册账号</el-button
>
<el-button
class="login-button"
type="info"
round
@click="backLogin"
>返回登录</el-button
>
</el-footer>
</el-card>
</div>
</template>
<script>
import { generaMenu } from "../../assets/js/menu";
import WaterRipple from "../water/waterRipple";
import waterBg from "../../images/water.png";
let canvasWidth = 600;
let canvasHeight = 600;
export default {
data: function () {
return {
loginForm: {
username: "",
password: "",
},
register: {
username: "",
email: "",
code: "",
confirmPassword: "",
password: "",
},
waterRipple: null,
timer: null,
sendTime: 60,
cardType: 1,
emailSendText: "发送",
rules: {
username: [
{
required: true,
message: "用户名不能为空",
trigger: "blur",
},
],
password: [
{
required: true,
message: "密码不能为空",
trigger: "blur",
},
],
},
};
},
mounted() {
if (this.$refs.boxRef && this.$refs.canvasRef) {
const { offsetWidth, offsetHeight } = this.$refs.boxRef;
canvasWidth = offsetWidth;
canvasHeight = offsetHeight;
this.$refs.canvasRef.width = canvasWidth;
this.$refs.canvasRef.height = canvasHeight;
const waterImg = new Image();
waterImg.src = waterBg;
this.waterRipple = new WaterRipple({
canvas: this.$refs.canvasRef,
background: waterImg,
boxRef: this.$refs.boxRef,
});
this.$refs.boxRef.style.backgroundImage = `url(${this.$refs.canvasRef.toDataURL()})`;
this.waterRipple.animate();
this.timer = setInterval(() => {
const x = Math.floor(canvasWidth * Math.random());
const y = Math.floor(canvasHeight * Math.random());
this.waterRipple.ripple(x, y);
}, 1000);
this.waterRipple.addMousemove();
}
},
methods: {
backLogin() {
this.cardType = 1;
},
sendCode() {
//发送邮件
this.countDown();
this.axios
.get("/api/users/code", {
params: { email: this.register.email },
})
.then(({ data }) => {
if (data.flag) {
this.$notify({
type: "success",
message: "发送成功",
});
} else {
this.$notify.error({
title: "发送验证码出现异常",
message: data.message,
});
}
});
},
countDown() {
this.flag = true;
this.timer = setInterval(() => {
this.sendTime--;
this.emailSendText = this.sendTime + "s";
if (this.sendTime <= 0) {
clearInterval(this.timer);
this.emailSendText = "发送";
this.sendTime = 60;
this.flag = false;
}
}, 1000);
},
login() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
const that = this;
// eslint-disable-next-line no-undef
var captcha = new TencentCaptcha(
this.config.TENCENT_CAPTCHA,
function (res) {
if (res.ret === 0) {
//发送登录请求
let param = new URLSearchParams();
param.append(
"username",
that.loginForm.username
);
param.append(
"password",
that.loginForm.password
);
that.axios
.post("/api/login", param)
.then(({ data }) => {
if (data.flag) {
// 登录后保存用户信息
that.$store.commit(
"login",
data.data
);
// 加载用户菜单
generaMenu();
that.$message.success("登录成功");
that.$router.push({ path: "/" });
} else {
that.$message.error(data.message);
}
});
}
}
);
// 显示验证码
captcha.show();
} else {
return false;
}
});
},
registerUser() {
this.axios.post("/api/register", this.register).then(({ data }) => {
if (data.flag) {
let param = new URLSearchParams();
param.append("username", this.register.username);
param.append("password", this.register.password);
this.axios.post("/api/login", param).then(({ data }) => {
this.username = "";
this.password = "";
this.$store.commit("login", data.data);
this.$store.commit("closeModel");
});
this.$notify({
type: "success",
message:
"用户:" + this.register.username + " 登录成功",
});
this.cardType = 1;
this.$router.push({ path: "/" });
} else {
this.$notify.error({
title: "登录出现错误",
message: data.message,
});
}
});
},
},
};
</script>
<style scoped>
.login-card {
position: absolute;
top: 50%;
left: 42%;
width: 15%;
padding: 1% 2% 2% 2%;
border-radius: 15px;
background-color: transparent;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
background: linear-gradient(to bottom, #67c23a, rgba(255, 255, 255, 0.6));
transform: translate(-50%, -50%);
z-index: 1;
-webkit-animation: bounce-in-top 1s both;
animation: bounce-in-top 1s both;
}
@-webkit-keyframes bounce-in-top {
0% {
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
opacity: 0;
}
38% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
opacity: 1;
}
55% {
-webkit-transform: translateY(-60%);
transform: translateY(-60%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
72% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
81% {
-webkit-transform: translateY(-55%);
transform: translateY(-55%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
90% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
95% {
-webkit-transform: translateY(-52%);
transform: translateY(-52%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
100% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
}
@keyframes bounce-in-top {
0% {
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
opacity: 0;
}
38% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
opacity: 1;
}
55% {
-webkit-transform: translateY(-60%);
transform: translateY(-60%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
72% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
81% {
-webkit-transform: translateY(-55%);
transform: translateY(-55%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
90% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
95% {
-webkit-transform: translateY(-52%);
transform: translateY(-52%);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
100% {
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
}
.form-item-flex {
display: flex;
align-items: center;
}
.el-input /deep/ .el-input__inner {
border-radius: 15px;
}
.footer-container {
display: flex;
justify-content: center;
align-items: center;
}
.login-button {
margin-right: 8px;
}
.el-card ::v-deep .el-card__body {
padding: 0;
}
.register-button {
margin-left: 8px;
}
.button-spacer {
width: 8px;
}
.login-form {
margin-top: 1.2rem;
padding-top: 5%;
}
.login-card button {
margin-top: 1rem;
width: 100%;
}
.el-input__inner {
border-radius: 15px;
}
</style>
<style lang = "scss" scoped>
.login-title {
display: flex;
justify-content: center;
align-items: center;
p {
font-family: "阿里妈妈";
color: #222;
position: relative;
&::before {
content: attr(data-text);
position: absolute;
color: #fff;
overflow: hidden;
white-space: nowrap;
border-right: 1px solid #fff;
animation: move 6s linear infinite;
filter: drop-shadow(0 0 20px #fff) drop-shadow(0 0 50px #fff);
}
@keyframes move {
0%,
10%,
100% {
width: 0;
}
70%,
90% {
width: 100%;
}
}
}
}
</style>
方法自行进行修改
1、点击下面地址 进入阿里巴巴图库:阿里巴巴矢量库地址
进行注册
2、创建一个项目
这个随心定义
生成在线地址
index 文件中引入地址
创建一个css
// 这里是你的项目图标前缀 根据刚刚你在阿里图库里面创建项目的时候设置的
[class^="el-icon-ali"], [class*="el-icon-ali"]
{
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
在main 中引入图库
后续持续更新项目 有兴趣就关注一波 对于新手和学生党比较友好 会发布项目实战中的一些业务逻辑
下一篇:
前后端登录逻辑实现权限登录 token 续时。有兴趣学习一些实战经验的伙伴可以关注一波