好玩的登录界面设计

效果示意图

效果不完整 后期会进一步优化动画效果就是感觉好玩 如果小伙伴有自己的项目 感觉有兴趣可以玩玩

效果图演示

使用技术

VUE + Element-Ui + Canvas 主要是这些 接下来说一下实现的拆分效果

Canvas

这个比较重要 主要是当成背景图进行使用 先说一下代码使用

这张图片当作背景图进行使用
好玩的登录界面设计_第1张图片
还有一张图片 增加背景丰富度
好玩的登录界面设计_第2张图片

这两张图片放到项目中进行备用

编写JS


// 这里是图片地址
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;

编写 vue

<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、创建一个项目
好玩的登录界面设计_第3张图片

这个随心定义
好玩的登录界面设计_第4张图片
生成在线地址
好玩的登录界面设计_第5张图片
index 文件中引入地址
好玩的登录界面设计_第6张图片
创建一个css
好玩的登录界面设计_第7张图片

// 这里是你的项目图标前缀 根据刚刚你在阿里图库里面创建项目的时候设置的
[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 中引入图库

好玩的登录界面设计_第8张图片

使用方法
好玩的登录界面设计_第9张图片

后续持续更新项目  有兴趣就关注一波 对于新手和学生党比较友好 会发布项目实战中的一些业务逻辑

下一篇:
前后端登录逻辑实现权限登录 token 续时。有兴趣学习一些实战经验的伙伴可以关注一波

你可能感兴趣的:(项目实战记录,vue.js,javascript,前端)