CCS+JS绘制星型拓扑图(关系图)

CCS+JS绘制星型拓扑图(关系图)

1. 需求

业务上,有时候需要展示与某一特定目标关联的其它相关元素,可能会用关系图来表示(专业名称为星型网络拓扑图),如下:
在这里插入图片描述

一般来说,像d3.jsEcharts 这些库都会有相应的实现,可以直接引用它们的API来实现。

但是,如果工程中只有这一处使用这种图表,不想因为这一个需求就引入一整个库,或者不想承受这些库所带来的性能开销(canvas绘图非常耗性能),那可以考虑本文将要介绍的这种使用纯CSS+原生JS 的方案尝试一下。

2. 需求分析

对于整个需求,我们可以分为两部分:

  • 中间的目标元素
    • 它由内外两层圈组成
    • 外圈带有渐变,内层是虚线边框
    • 外圈逆时针旋转, 外层逆时针旋转
  • 周围的关联元素
    • 与目标元素之间有连线
    • 均匀分布在目标元素周围

下面就分步骤来分别实现这两个需求

3. 目标元素

环形渐变

一开始,能想到的方案是外层一个正方形,然后设置一定宽度的边框,边框border-image 属性设置为一个渐变色,但是经过尝试,边框的渐变色无法达到预期的均匀分布的效果,故放弃,采取外圆-内圆=环形 的方案。

  1. 首先我们有一个外层盒子
  2. 然后里面有一个内层盒子,内层盒子比外层盒子稍小
  3. 然后外层盒子设置一个背景色并利用圆角边框属性裁剪为圆形,
  4. 内层盒子设置背景色为白色并利用圆角边框属性裁剪为圆形
  5. 通过定位将内层盒子放在合适的位置,形成一个环,代码与效果如下:
<div class="outer-box">
	<div class="inner-box"></div>
</div>

<style>

  .outer-box {
    width: 200px;
    height:200px;
    position: relative;
    border-radius: 50% 50%;
    background: blue;
  }
  
  .inner-box {
    width: 180px;
    height: 180px;
    position: absolute;
    left:10px;
    top:10px;
    background-color: #fff;
    border-radius: 50% 50%;
  }

</style>

效果如下:

image-20201021093154274

外部圆环就出来了。

这里的关键点是

但是这个圆环要是一个均匀分布的渐变色, 即使设置外层盒子background-image属性为一个渐变色,也达不到需求所要的效果,怎么办呢?

仔细观察需求,其实是一组渐变色在圆环上顺着时针方向重复了四次。那假设组成这组渐变的四个颜色点分别是#1,#2,#3,#4,它们在圆环上其实是像下面这样排列的:
在这里插入图片描述

既然这样,我们完全可以把它们分成四份,每一份的背景色都是由 #1、#2、#3、#4组成的渐变,只不过渐变的方向不同而已。那我们就在外层盒子中再放入四个小盒子,每个盒子大小占外层盒子的四分之一,然后设置同样的渐变色不同的渐变方向:

<div class="outer-box">
  <div class="left-top"></div>
  <div class="right-top"></div>
  <div class="left-bottom"></div>
  <div class="right-bottom"></div>
  <div class="inner-box"></div>
</div>

<style>
	.left-top, .right-top, .left-bottom, .right-bottom {
    width: 100px;
    height: 100px;
    position: absolute;
  }
   .left-top {
     background: linear-gradient(to top right, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
     top:0;
     left:0;
  }

  .right-top {
    background: linear-gradient(to bottom right, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
    top:0;
    left:100px;
  }
  .left-bottom {
    background: linear-gradient(to left top, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
    left:0;
    top:100px;
  }
  .right-bottom {
    background: linear-gradient(to left bottom, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
    left: 100px;
    top: 100px;
  }
</style>

可以看到效果如下:

image-20201021094538015

可是,由于border-radius 只作用于边框,边框变性后并不影响内容的展示,所以我们发现四个小盒子其实没有形成圆环,我们有两种方案解决这个问题:

方案一:利用overflow属性

.outer-box {
	width: 200px;
  height:200px;
  position: relative;
  border-radius: 50% 50%
  background: blue;
	ovarflow: hidden;
}

方案二: 利用clip-path,可以不再使用圆角边框

.outer-box {
            width: 200px;
            height:200px;
            position: relative;
            clip-path: circle(50% at 50% 50%); 
            background: blue;
        }

完成后效果如下:

在这里插入图片描述

可以看到,外层圆环基本成型。再给它加上小圆点,就完全符合需求了:

<div class="outer-box">
  <div class="left-top">
    <div class="litle-circle"></div>
  </div>
  <div class="right-top">
    <div class="litle-circle"></div>
  </div>
  <div class="left-bottom">
    <div class="litle-circle"></div>
  </div>
  <div class="right-bottom">
    <div class="litle-circle"></div>
  </div>
  <div class="inner-box"></div>
</div>

<style>
	 .litle-circle {
     width:10px;
     height: 10px;
     background-color: #4094FF;
     clip-path: circle(50% at 50% 50%);
  }
  .left-top .litle-circle {
    margin-top:90px;
    margin-left: 0;
  }
  .right-top .litle-circle {
    margin-top: 0;
    margin-left: 0;
  }

  .left-bottom .litle-circle {
    margin-top:90px;
    margin-left: 90px;
  }
  .right-bottom .litle-circle {
    margin-left: 90px;
    margin-top: 0;
  }
</style>

最终环形渐变如下:

在这里插入图片描述

然后让它动起来,就是添加一个动画效果:

.outer-box {
  width: 200px;
  height:200px;
  position: relative;
  clip-path: circle(50% at 50% 50%);
  background: blue;
  animation: spin 6s linear infinite;
}

@keyframes spin{
  to {
    transform: rotate(-1turn);
  }
}

其中,-1turn 表示逆时针旋转一周。

效果如下
在这里插入图片描述

内部圈

然后添加内部转动的圈,这个内部圈可以加到内部盒子里:

<div class="inner-box">
  <div class="inner-rotate-box">
  </div>
</div>

<style>
	.inner-rotate-box {
    width: 150px;
    height: 150px;
    left:15px;
    top: 15px;
    position:absolute;
    border:4px dashed blue;
    border-radius: 50% 50%;
    box-sizing: border-box;
  }
</style>

主要是利用border:4px dashed blue 给它设置一个虚线边框

效果:
在这里插入图片描述

我们发现内层是跟着外层转动的,而需求时内层要与外层相反,顺时针旋转,我们通过设置动画参数来让它反向旋转:

.inner-rotate-box {
	animation: spin 12s linear infinite;
  animation-direction: reverse;
}

我们发现,其实没有效果,那是因为它的外层盒子inner-box 还是跟着outer-box一起旋转的,我们给它的外层盒子也加上反向旋转:

.inner-box {
  animation: inherit;
  animation-direction: reverse;
}

通过继承outer-box的动画并进行反向运动,可以抵消outer-box带给inner-box的旋转动画,这时看起来inner-box就像是没有在旋转一样,而它内部的旋转盒子又是与外层盒子反向旋转的,就能实现内外相反运动的效果:
在这里插入图片描述

接着,我们再给内层旋转的圆添加一层点状虚线圈:


<div class="inner-box">
  <div class="inner-rotate-box"></div>
  <div class="inner-dotted-box"></div>          
</div>

<style>
	.inner-dotted-box {
    width: 154px;
    height:154px;
    position:absolute;
    top:13px;
    left:13px;
    border: 2px dotted #B5D5FF;
    border-radius: 50% 50%;
    box-sizing: border-box;
  }
</style>

这里关键点有两个:

  • 这里的虚线是点状虚线,所有边框样式用dotted;
  • 这里和上面的inner-rotate-box 都要设置box-sizingborder-box, 以便使我们的位置计算更为精确;

效果如下:
在这里插入图片描述

最后,我们把内容添加上,注意,内容盒子要和旋转盒子和点状虚线盒子同级,直接放在内层盒子里,原因是我们上面说过的,内层盒子inner-box通过与外层盒子反向旋转抵消了旋转,而看起来就像是没有旋转一样,内容只有放在它里面,才不会跟着旋转。

<div class="inner-box">
  <div class="inner-rotate-box"></div>
  <div class="inner-dotted-box"></div>
  <div class="inner-content">
    目标内容
  </div>        
</div>

<style>
	.inner-content {
    width: 120px;
    height: 120px;
    position: absolute;
    top: 30px;
    left: 30px;
    display: flex;
    justify-content: center;
    align-items: center;
  }

</style>

效果如下:
在这里插入图片描述

下面是整个目标元素的实现代码,可供参考:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>双层圆环反向圆周运动示例</title>
    <style>
        .outer-box {
            width: 200px;
            height:200px;
            position: relative;
            clip-path: circle(50% at 50% 50%);
            border-radius: 50% 50%;
            background: blue;
            animation: spin 6s linear infinite;
        }
        
        .inner-box {
            width: 180px;
            height: 180px;
            position: absolute;
            left:10px;
            top:10px;
            background-color: #fff;
            border-radius: 50% 50%;
            animation: inherit;
            animation-direction: reverse;
        }

        .left-top, .right-top, .left-bottom, .right-bottom {
            width: 100px;
            height: 100px;
            position: absolute;
        }
        .left-top {
            background: linear-gradient(to top right, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
            top:0;
            left:0;
        }

        .right-top {
            background: linear-gradient(to bottom right, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
            top:0;
            left:100px;
        }
        .left-bottom {
            background: linear-gradient(to left top, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
            left:0;
            top:100px;
        }
        .right-bottom {
            background: linear-gradient(to left bottom, #C3DDFF 25%, #F1f7ff 100%, #78B3FF 75%, #98C5FF 100%);
            left: 100px;
            top: 100px;
        }
        .litle-circle {
            width:10px;
            height: 10px;
            background-color: #4094FF;
            clip-path: circle(50% at 50% 50%);
        }
        .left-top .litle-circle {
            margin-top:90px;
            margin-left: 0;
        }
        .right-top .litle-circle {
            margin-top: 0;
            margin-left: 0;
        }
        .left-bottom .litle-circle {
            margin-top:90px;
            margin-left: 90px;
        }
        .right-bottom .litle-circle {
            margin-left: 90px;
            margin-top: 0;
        }
        .inner-rotate-box {
            width: 150px;
            height: 150px;
            left:15px;
            top: 15px;
            position: absolute;
            border:4px dashed blue;
            border-radius: 50% 50%;
            box-sizing: border-box;
            animation: spin 12s linear infinite;
            animation-direction: reverse;
        }
        
        .inner-dotted-box {
            width: 154px;
            height:154px;
            position:absolute;
            top:13px;
            left:13px;
            border: 2px dotted #B5D5FF;
            border-radius: 50% 50%;
            box-sizing: border-box;
        }

        .inner-content {
            width: 120px;
            height: 120px;
            position: absolute;
            top: 30px;
            left: 30px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        @keyframes spin{
            to {
                transform: rotate(-1turn);
            }
        }

    </style>
</head>
<body>
    <div class="outer-box">
        <div class="left-top">
            <div class="litle-circle"></div>
        </div>
        <div class="right-top">
            <div class="litle-circle"></div>
        </div>
        <div class="left-bottom">
            <div class="litle-circle"></div>
        </div>
        <div class="right-bottom">
            <div class="litle-circle"></div>
        </div>
        <div class="inner-box">
            <div class="inner-rotate-box"></div>
            <div class="inner-dotted-box"></div>
            <div class="inner-content">
                目标内容
            </div>        
        </div>
    </div>
</body>
</html>

4. 关联元素

对于关联元素,则相对复杂一点,我们要考虑的是:

  • 关联元素个数不定,可能很少,也可能很多,所以少的时候就要大一点,多的时候就要小一点,这就需要计算关联元素的宽度,又由于都是圆形展示,所以都是一个正方形盒子裁切成圆形
  • 关联元素要围绕目标元素均匀分布
  • 关联元素与目标元素之间要有连线

基于以上几个点,我们设计了如下算法:

4.1 容器区块划分

我们把整个外层容器设计成一个正方形,然后使用水平平分线、垂直平分线和两条对角线将它划分为8个区块,加上这几条线本身切割形成的8条线也算一个区块,总共16个区块:
在这里插入图片描述

4.2 根据数量计算关联元素尺寸、弧度及角度

根据关联元素的数量,计算关联元素的尺寸、弧度和角度

即如果有N个关联元素,并且N> 2, 则我们认为可以将N个元素分别绘制到容器上半部分和下半部分,即将所有元素平分到两个数组里,如果是奇数个,则上半部分的数组里就多一个。

const rowLength = Math.ceil(this.unitRelations.length / 2); // 向上取整
const row1 = [];
const row2 = [];
for (let j = 0; j < this.unitRelations.length; j++) {
j < rowLength ? row1.push(j) : row2.push(j);
}

然后计算它的尺寸,就是看元素所在那半部分一共有几个元素,然后设置一定的间隔距离,用整个容器的宽度减去它们之间的间隔的总和,再除以这半部分的元素个数,就获得了它的尺寸,当然,我们这里还需要设置一个最大尺寸,防止它的尺寸太大,在视觉上显得比目标元素还要重要。

const maxSize = 100;
const originalSize = this.graphWidth / rowLength;
const gap = originalSize / 10; // 间隔设置为元素无间隔平分区域时的大小的十分之一
const size = (this.graphWidth - gap * (rowLength - 1)) / rowLength;
const result = size > maxSize ? maxSize : size;

然后计算出每个元素平均的弧度和角度:

const baseRadian = parseInt(360 / this.unitRelations.length); // 每一份的基本弧度
const radian = 30 + baseRadian * i; // 获得旋转弧度,我们将起始点设置为30度
const minRadian = this.isInRow([1, 3, 5, 7], this.getBlockIndex(radian)) ? radian % 45 : 45 - radian % 45;
const angle = minRadian * Math.PI / 180; // 获得弧度相应的角度

这里,minRadian 其实是用来计算当前元素参与位置计算时它的实际弧度,由于我们要用直角三角形的三角函数来计算它的位置,所以它的基础弧度要小于45度,而它处在不同区块时,用来计算的弧度也是不一样的:

  • 如果它处于1/3/5/7区块,则参与计算的弧度就是它取模后的弧度
  • 如果它处于2/4/6/8区块,则参与计算的弧度是它的弧度取模后与45的插值

如图:
在这里插入图片描述

分别落在第3和第4区块内的两个元素,第3区块参与计算的弧度就是它弧度取模后的德尔塔1,而第4区块的元素参与计算的弧度实际上是取模后的弧度德尔塔2与区块弧度45之间的差值德尔塔3。

有了元素尺寸和参与计算的角度,我们又有容器的宽高, 根据上图,我们很容易就可计算出元素距容器左边和上边的距离,这样元素的位置就确定了。

具体计算过程需要用到三角函数,可以自行计算。

边框吸附

注意上图,我们之所以能够对元素位置进行计算,还用到了一个技巧,就是边框吸附,我们默认关联元素时吸附在外层容器边框上的,当然,根据其所处区块不同,其吸附的边有可能是上、下、左、右其中一边,

代码如下:

const { radian, opposite } = this.getBaseProperty(i);
            const block = this.getBlockIndex(radian);
            if (block === 1) {
                return {
                    top: this.graphWidth / 2 - opposite - width / 2,
                    left: 0
                };
            } else if (block === 2) {
                return {
                    top: 0,
                    left: this.graphWidth / 2 - opposite - width / 2
                };
            } else if (block === 3) {
                return {
                    top: 0,
                    left: this.graphWidth / 2 + opposite - width / 2
                };
            } else if (block === 4) {
                return {
                    left: this.graphWidth - width,
                    top: this.graphWidth / 2 - opposite - width / 2
                };
            } else if (block === 5) {
                return {
                    left: this.graphWidth - width,
                    top: this.graphWidth / 2 + opposite - width / 2
                };
            } else if (block === 6) {
                return {
                    left: this.graphWidth / 2 + opposite - width / 2,
                    top: this.graphWidth - width
                };
            } else if (block === 7) {
                return {
                    left: this.graphWidth / 2 - opposite - width / 2,
                    top: this.graphWidth - width
                };
            } else if (block === 8) {
                return {
                    left: 0,
                    top: this.graphWidth / 2 + opposite - width / 2
                };
            } else if (block === 1.5) {
                return {
                    left: 0,
                    top: 0
                };
            } else if (block === 2.5) {
                return {
                    left: this.graphWidth / 2 - width / 2,
                    top: 0
                };
            } else if (block === 3.5) {
                return {
                    left: this.graphWidth - width / 2,
                    top: 0
                };
            } else if (block === 4.5) {
                return {
                    left: this.graphWidth - width,
                    top: this.graphWidth / 2 - width / 2
                };
            } else if (block === 5.5) {
                return {
                    left: this.graphWidth - width / 2,
                    top: this.graphWidth - width
                };
            } else if (block === 6.5) {
                return {
                    left: this.graphWidth / 2 - width / 2,
                    top: this.graphWidth - width
                };
            } else if (block === 7.5) {
                return {
                    left: 0,
                    top: this.graphWidth - width
                };
            } else if (block === 8.5) {
                return {
                    left: 0,
                    top: this.graphWidth / 2 - width / 2
                };
            }

可以看到,不同的区块,其吸附的边不一样,具体表现就是直接设置其left为0或top为0,或者left为容器宽度减去元素尺寸或者容器高度(其实也就是容器宽度)减去元素尺寸等等;

连线

对于连线,我们设计为关联元素内部的一个绝对定位的元素:

 <li v-for="(site, index) in unitRelations" :key="index" :style="getStyle(index)">
                            <div class="connector" :style="getConnectStyle(index)">
                                <span class="one" :style="getDirect(index)"></span>
                                <span class="two" :style="getDirect(index)"></span>
                                <p>{{ site.unitType }}</p>
                            </div>
                            <div class="site-info">
                                <p>{{ site.mediaName }} <br /> {{ site.siteName }}</p>
                            </div>
                        </li>

上面是Vue项目中应用的代码片段,其中类名为connect的元素就是连线。类名为onetwo的两个元素就是需求中连线上不断运动的小点,这个很好实现,就不讲了,主要讲下连线的长度和角度计算。

连线默认其实就是关联元素中一个绝对定位的长方形,它的固定高度可以设为2-5像素(根据需要)来模拟一根线,然后初始高度就是元素的高度的一半位置,而水平偏移量则根据其所在不同区块而不同:如图
在这里插入图片描述

然后我们计算它的宽度,由于我们已经确定了关联元素的位置和角度,就可以直接根据元素位置的left值和top值,结合容器的宽高,利用三角函数,计算出元素圆心到目标元素圆心的连线距离,如图所示:

image-20201021145043124

计算出连线长度后,上文所说的连线初始水平偏移量也就是它的长度(或负的长度,根据偏移方向不同),

然后将它旋转和关联元素一样的弧度,就可以了。

当然,关联元素的大小、位置算法还很基础,有兴趣的同学可以尝试更好的算法,比如各个元素的尺寸可以增加一定弹性系数,呈现出有大有小,近大远小的视觉效果,位置也可以不用吸附,而是根据区块内元素拥挤程度,适当调节某些元素的位置,有的可以离目标近点,有的可以离目标远一点。

由于这部分算法还不算最优,就补贴具体代码了,读者可以根据以上思路和关键点自行进行开发和算法优化。

### 回答1: HTML、CSSJS是构建登录注册页面的常用技术。 HTML(超文本标记语言)是用于构建页面结构和内容的标记语言,能够创造出标题、段落、列表等基本元素。在登录注册页面中,可以使用HTML来创建输入框、按钮、表单等元素,以便用户输入账号密码信息。 CSS(层叠样式表)用于设置页面的样式和布局,可以通过CSS定义元素的颜色、字体、大小、边框等样式属性,使登录注册页面具有美观的外观和用户友好的交互效果。 JavaScript(JS)是一种脚本语言,可以用于网页的动态交互和动态效果。在登录注册页面中,可以使用JavaScript来验证用户输入的账号密码是否符合要求,并进行相关提示或操作,例如检查密码强度、检查用户名是否已存在等。 在登录注册页面的开发中,可以先使用HTML构建页面的基本结构,包括输入框、按钮和表单等元素。然后,通过CSS设置页面的样式,使页面具有吸引人的外观。最后,使用JavaScript编写相应的逻辑代码,实现用户输入的验证和交互效果。 总之,HTML、CSSJS是登录注册页面开发中不可或缺的技术,通过它们的结合使用,可以实现一个功能完善、外观美观的登录注册页面。 ### 回答2: HTML是一个网页的结构语言,它定义了网页的内容和组织结构。CSS是一种样式语言,用于定义网页的外观和布局。而JavaScript是一种脚本语言,用于实现网页的交互功能和动态效果。 登录注册页面是网站常见的功能之一,它允许用户创建账号并登录。下面是一个简单的登录注册页面的示例: HTML部分: ```html <!DOCTYPE html> <html> <head> <title>登录注册页面</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <div class="container"> <h1>欢迎登录</h1> <form id="loginForm"> <label for="username">用户名:</label> <input type="text" id="username" required> <br> <label for="password">密码:</label> <input type="password" id="password" required> <br> <input type="submit" value="登录"> </form> <h1>创建新账号</h1> <form id="registerForm"> <label for="newUsername">用户名:</label> <input type="text" id="newUsername" required> <br> <label for="newPassword">密码:</label> <input type="password" id="newPassword" required> <br> <input type="submit" value="注册"> </form> </div> <script src="script.js"></script> </body> </html> ``` CSS部分(styles.css): ```css .container { width: 300px; margin: 0 auto; } h1 { text-align: center; } form { margin-bottom: 20px; } label { display: block; } input[type="submit"] { margin-top: 10px; } ``` JavaScript部分(script.js): ```javascript document.getElementById("loginForm").addEventListener("submit", function(event) { event.preventDefault(); // 阻止表单的默认提交行为 var username = document.getElementById("username").value; var password = document.getElementById("password").value; // 在这里进行登录验证,比如通过Ajax请求发送到后端进行验证 console.log("登录信息:", username, password); }); document.getElementById("registerForm").addEventListener("submit", function(event) { event.preventDefault(); var newUsername = document.getElementById("newUsername").value; var newPassword = document.getElementById("newPassword").value; // 在这里进行注册逻辑,比如创建新用户并保存到数据库中 console.log("注册信息:", newUsername, newPassword); }); ``` 以上是一个简单的登录注册页面的示例,用户可以输入用户名和密码进行登录,或者创建新账号进行注册。利用HTML定义页面结构,CSS设置样式,JavaScript实现登录和注册逻辑,实现了用户交互的功能。当用户点击登录或注册按钮时,对应的表单将会提交,并通过JavaScript进行处理。用户输入的信息可以通过JavaScript代码进行验证、保存或其他操作。 ### 回答3: HTML、CSS和JavaScript组成了登录注册页面的基本架构。 HTML用于创建页面的结构和内容,CSS用于设置页面的样式和布局,JavaScript用于实现页面的交互和动态效果。 登录注册页面需要包含以下内容: 1. 标题和Logo:使用HTML来创建页面的标题和Logo,可以使用h1标签添加标题,img标签添加Logo。 2. 表单:使用HTML中的form标签创建登录和注册表单。表单包含输入框和提交按钮,可以使用input标签创建输入框,并设置type属性为"text"或"password",使用button或input标签创建提交按钮。 3. 样式:使用CSS来设置页面的样式,可以设置背景颜色、字体样式、输入框样式、按钮样式等。可以使用选择器来选择不同的元素,并设置相应的样式。 4. 验证:使用JavaScript来实现表单的验证。可以使用JavaScript中的事件监听器来监听表单提交事件,通过获取表单输入框的值进行验证,如检查输入框是否为空、密码是否符合要求等。可以使用alert弹窗或修改页面元素来显示验证结果。 5. 动态效果:使用JavaScript来实现页面的动态效果,如输入框聚焦时的样式变化、页面的过渡效果等。使用JavaScript中的事件监听器来监听输入框的聚焦事件,并通过修改元素的样式来实现效果。 总结: 通过HTML创建页面的结构和内容,通过CSS设置页面的样式和布局,通过JavaScript实现页面的交互和动态效果,可以创建出一个功能完善的登录注册页面。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值