使用HTML5和CSS3实现雷达扫描超炫丽效果,它的一大特点是3D立体的视觉效果,鼠标点击雷达后将会展现一张3D立体地图,并且对地图上指定的几个地点进行坐标详细信息描述。
下面我讲解一下我是怎么写的
HTML代码
首先HTML代码非常简单,只需在页面上展示一个div容器即可。
<div class="radar"></div>
CSS代码
这里主要利用了CSS3的旋转等动画特性,看看以下的CSS代码,主要是完成所有的动画:
@-webkit-keyframes flashing {
0%,
30% {
-webkit-transform: scale(1);
transform: scale(1);
}
70%,
100% {
-webkit-transform: scale(0);
transform: scale(0);
}
}
@keyframes flashing {
0%,
30% {
-webkit-transform: scale(1);
transform: scale(1);
}
70%,
100% {
-webkit-transform: scale(0);
transform: scale(0);
}
}
@-webkit-keyframes pulsation {
0% {
-webkit-transform: scale(0);
transform: scale(0);
border-color: red;
background-color: rgba(255, 0, 0, 0.6);
}
80%,
100% {
-webkit-transform: scale(1);
transform: scale(1);
border-color: rgba(255, 0, 0, 0.2);
background-color: rgba(255, 0, 0, 0.1);
}
}
@keyframes pulsation {
0% {
-webkit-transform: scale(0);
transform: scale(0);
border-color: red;
background-color: rgba(255, 0, 0, 0.6);
}
80%,
100% {
-webkit-transform: scale(1);
transform: scale(1);
border-color: rgba(255, 0, 0, 0.2);
background-color: rgba(255, 0, 0, 0.1);
}
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg) translateZ(1px);
transform: rotate(0deg) translateZ(1px);
}
to {
-webkit-transform: rotate(360deg) translateZ(1px);
transform: rotate(360deg) translateZ(1px);
}
}
@keyframes rotation {
from {
-webkit-transform: rotate(0deg) translateZ(1px);
transform: rotate(0deg) translateZ(1px);
}
to {
-webkit-transform: rotate(360deg) translateZ(1px);
transform: rotate(360deg) translateZ(1px);
}
}
@-webkit-keyframes scale {
from {
-webkit-transform: scale(1);
transform: scale(1);
}
to {
-webkit-transform: scale(1.3);
transform: scale(1.3);
}
}
@keyframes scale {
from {
-webkit-transform: scale(1);
transform: scale(1);
}
to {
-webkit-transform: scale(1.3);
transform: scale(1.3);
}
}
@-webkit-keyframes polish {
30%,
100% {
-webkit-transform: skewX(35deg) translateX(380%);
transform: skewX(35deg) translateX(380%);
}
}
@keyframes polish {
30%,
100% {
-webkit-transform: skewX(35deg) translateX(380%);
transform: skewX(35deg) translateX(380%);
}
}
当然这个雷达光是有动画是不行的,还得有漂亮的外观,用以下CSS代码实现:
.radar { height: 40em; width: 48em; position: relative; cursor: pointer; -webkit-perspective: 62.5em; perspective: 62.5em; } .radar .radar-map-container, .radar .risk-points, .radar .scanning-circle canvas { left: 0; top: 0; position: absolute; } .radar .radar-map-container, .radar .radar-map, .radar .scanning-circle, .radar .risk-elements-group { -webkit-transition: opacity 2.5s ease-out, -webkit-transform 2.5s ease-out, -webkit-transform-origin; transition: opacity 2.5s ease-out, -webkit-transform 2.5s ease-out, -webkit-transform-origin; transition: transform 2.5s ease-out, opacity 2.5s ease-out, transform-origin; transition: transform 2.5s ease-out, opacity 2.5s ease-out, transform-origin, -webkit-transform 2.5s ease-out, -webkit-transform-origin; } .radar .radar-map-container, .radar .radar-map, .radar .scanning-circle { will-change: transform; } .radar .radar-map-container { width: 400%; height: 400%; left: -150%; top: -150%; -webkit-transform: scale(0.25); transform: scale(0.25); -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } .radar .radar-map { color: rgba(19, 182, 206, 0.4); background: url('http://i64.tinypic.com/5l17ut.jpg') center / contain no-repeat; width: 100%; height: 100%; -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } .radar .radar-map.roaming { -webkit-transition-duration: 5s !important; transition-duration: 5s !important; -webkit-transition-timing-function: ease-in-out; transition-timing-function: ease-in-out; -webkit-transition-delay: .5s; transition-delay: .5s; } .radar .radar-map .risk-elements-group { opacity: 0; -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } .radar .radar-map .risk-elements-group .red-flag, .radar .radar-map .risk-elements-group .info-panel, .radar .radar-map .risk-elements-group .dashed-circle { position: absolute; } .radar .radar-map .risk-elements-group .red-flag, .radar .radar-map .risk-elements-group .dashed-circle { visibility: hidden; } .radar .radar-map .risk-elements-group .red-flag, .radar .radar-map .risk-elements-group .info-panel { -webkit-transform-origin: bottom; transform-origin: bottom; -webkit-transform: rotateX(-70deg); transform: rotateX(-70deg); } .radar .radar-map .risk-elements-group .red-flag { width: 0.5em; height: 10em; background-color: white; -webkit-transform: rotateX(-70deg) rotateZ(-90deg); transform: rotateX(-70deg) rotateZ(-90deg); } .radar .radar-map .risk-elements-group .red-flag.stand-up { -webkit-transform: rotateX(-70deg); transform: rotateX(-70deg); visibility: visible; } .radar .radar-map .risk-elements-group .red-flag:before { content: attr(data-city); font-weight: bold; color: white; position: absolute; background-color: red; -webkit-clip-path: polygon(0 0, 100% 50%, 0 100%); -moz-clip-path: polygon(0 0, 100% 50%, 0 100%); clip-path: polygon(0 0, 100% 50%, 0 100%); width: 4.782608695652174em; height: 3.4782608695652173em; line-height: 3.4782608695652173em; font-size: 1.4375em; font-family: 微软雅黑; letter-spacing: 0.43478260869565216em; padding-left: 0.5217391304347826em; -webkit-box-sizing: border-box; box-sizing: border-box; top: 0.43478260869565216em; white-space: nowrap; } .radar .radar-map .risk-elements-group .red-flag:after { content: ''; position: absolute; width: 2.1875em; height: 0.625em; border-radius: 50%; background-color: inherit; top: calc(100% - 0.625em); left: calc(50% - 1.09375em); } .radar .radar-map .risk-elements-group .info-panel { -webkit-transform: rotateX(-70deg) translateY(-30%); transform: rotateX(-70deg) translateY(-30%); -webkit-filter: opacity(0); -moz-filter: opacity(0); filter: opacity(0); border: 0.0625em solid #DCDA6B; border-radius: 0.25em; background-color: rgba(245, 228, 158, 0.31); display: table; font-weight: bold; font-size: 2.5em; text-shadow: 0.025em 0.025em 0.05em black; padding: 0.2em 0.3em; font-family: 黑体; overflow: hidden; } .radar .radar-map .risk-elements-group .info-panel.showup { -webkit-filter: opacity(1); -moz-filter: opacity(1); filter: opacity(1); transition: -webkit-filter 1s linear 0.5s, -moz-filter 1s linear 0.5s, filter 1s linear 0.5s, opacity 0.5s linear; } .radar .radar-map .risk-elements-group .info-panel.polish:after { -webkit-animation: polish 5s linear 1 1s; animation: polish 5s linear 1 1s; } .radar .radar-map .risk-elements-group .info-panel .info-title { background-image: -webkit-gradient(linear, left top, right top, from(rgba(208, 209, 120, 0.6)), to(rgba(223, 226, 183, 0.05))); background-image: linear-gradient(to right, rgba(208, 209, 120, 0.6), rgba(223, 226, 183, 0.05)); color: #FFE401; padding: 0 0.275em; border-radius: inherit; } .radar .radar-map .risk-elements-group .info-panel .info-content { color: white; margin: 0.25em; line-height: 1.3em; } .radar .radar-map .risk-elements-group .info-panel:after { content: ''; position: absolute; width: 30%; height: 100%; -webkit-transform: skewX(35deg) translateX(-160%); transform: skewX(35deg) translateX(-160%); top: 0; background-image: -webkit-gradient(linear, left top, right top, from(transparent), color-stop(rgba(255, 255, 255, 0.3)), to(transparent)); background-image: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.3), transparent); } .radar .radar-map .risk-elements-group .dashed-circle { width: 7.5em; height: 7.5em; border-radius: 50%; background: center / contain no-repeat; -webkit-animation: rotation 5s linear infinite; animation: rotation 5s linear infinite; -webkit-transition-property: visibility; transition-property: visibility; } .radar .radar-map .risk-elements-group .dashed-circle:after { content: ''; border-radius: inherit; margin: auto; width: 15%; height: 15%; background-color: #E03636; -webkit-box-shadow: 0 0 0.9375em black; box-shadow: 0 0 0.9375em black; -webkit-animation: scale 1s linear infinite alternate; animation: scale 1s linear infinite alternate; position: absolute; left: 0; right: 0; top: 0; bottom: 0; } .radar .changing-number-container { position: absolute; right: 12%; top: 10%; bottom: 0; margin: auto; } .radar .changing-number-container { display: table; color: #10ABE0; } .radar .changing-number-container:before, .radar .changing-number-container:after { display: inline-block; } .radar .changing-number-container:before { content: ''; background: -webkit-gradient(linear, left top, left bottom, color-stop(45%, transparent), color-stop(55%, currentColor), color-stop(55%, transparent)) center / 100% 1em; background: linear-gradient(to bottom, transparent 45%, currentColor 55%, transparent 55%) center / 100% 1em; width: 1em; height: 100%; margin-right: 0.625em; } .radar .changing-number-container:after { font-size: 0.75em; content: attr(data-number); width: 1.3333333333333333em; line-height: 1.3333333333333333em; word-break: break-all; letter-spacing: 1.3333333333333333em; vertical-align: top; font-weight: bold; } .radar .risk-points { z-index: 15; } .radar .risk-points .risk-point-group .risk-point { position: absolute; width: 0.625em; height: 0.625em; border-radius: 50%; -webkit-filter: blur(2px); } .radar .risk-points .risk-point-group .risk-point:after { content: ''; display: block; height: 100%; border-radius: 50%; will-change: transform; -webkit-transform: scale(0); transform: scale(0); } .radar .risk-points .risk-point-group .risk-point.type1:after { -webkit-box-shadow: 0 0 0.3125em 0.3125em white inset, 0 0 0.3125em 0.5625em rgba(245, 76, 128, 0.54), 0 0 2.5625em 1.5625em rgba(222, 17, 17, 0.89); box-shadow: 0 0 0.3125em 0.3125em white inset, 0 0 0.3125em 0.5625em rgba(245, 76, 128, 0.54), 0 0 2.5625em 1.5625em rgba(222, 17, 17, 0.89); } .radar .risk-points .risk-point-group .risk-point.type2:after { -webkit-box-shadow: 0 0 0.3125em 0.3125em white inset, 0 0 0.3125em 0.5625em #15d8e8, 0 0 2.5625em 1.5625em rgba(44, 218, 226, 0.89); box-shadow: 0 0 0.3125em 0.3125em white inset, 0 0 0.3125em 0.5625em #15d8e8, 0 0 2.5625em 1.5625em rgba(44, 218, 226, 0.89); } .radar .risk-points .risk-point-group .risk-point.critical { -webkit-transform: scale(1); transform: scale(1); } .radar .risk-points .risk-point-group .risk-point.ordinary { -webkit-transform: scale(0.4); transform: scale(0.4); } .radar .risk-points .risk-point-group .risk-point.pulsation { color: red; -webkit-filter: none; width: 5em; height: 5em; } .radar .risk-points .risk-point-group .risk-point.pulsation .pulse-circle { position: absolute; width: 100%; height: 100%; border-radius: 50%; -webkit-box-sizing: border-box; box-sizing: border-box; border: 0.1875em solid currentColor; -webkit-animation: pulsation 3s linear 1; animation: pulsation 3s linear 1; } .radar .risk-points .risk-point-group .risk-point.pulsation .pulse-circle:nth-child(2) { -webkit-animation-delay: .8s; animation-delay: .8s; } .radar .risk-points .risk-point-group .risk-point.pulsation .pulse-circle:nth-child(3) { -webkit-animation-delay: 1.6s; animation-delay: 1.6s; } .radar .risk-points .risk-point-group .risk-point.pulsation:after { content: ''; position: absolute; width: 15%; height: 15%; border-radius: 50%; background-color: currentColor; left: 0; right: 0; top: 0; bottom: 0; margin: auto; } .radar .risk-points .risk-point-group.flashing .risk-point:after { -webkit-animation: flashing 2s linear 1; animation: flashing 2s linear 1; } .radar .scanning-circle { position: relative; height: 100%; } .radar .scanning-circle .radar-scanner { width: 40em; height: 100%; margin: 0 auto; position: relative; z-index: 10; -webkit-transform: rotate(125deg); transform: rotate(125deg); } .radar .scanning-circle .radar-scanner .inner-scanner, .radar .scanning-circle .radar-scanner .outer-scanner { width: 100%; height: 100%; } .radar .scanning-circle .radar-scanner .inner-scanner { display: none; position: absolute; -webkit-clip-path: inset(0 0 0 50%); -webkit-filter: blur(30px); -webkit-animation: rotation 30s linear infinite; animation: rotation 30s linear infinite; } .radar .scanning-circle .radar-scanner .inner-scanner:after { content: ''; -webkit-clip-path: inset(0 50% 0 0); display: block; height: 100%; background-color: rgba(19, 182, 206, 0.3); -webkit-transform: rotate(30deg); transform: rotate(30deg); border-radius: 50%; } .radar .scanning-circle .radar-scanner .outer-scanner { -webkit-clip-path: circle(20em at 50% 48%); -moz-clip-path: circle(20em at 50% 48%); clip-path: circle(20em at 50% 48%); -webkit-box-sizing: border-box; box-sizing: border-box; border: 0 solid white; border-top-width: 5px; border-radius: 50%; -webkit-box-shadow: 0 -0.1875em 0.3125em #33C9E8, 0 0 0.9375em white, 0 0.3125em 0.5em #38C1D2 inset; box-shadow: 0 -0.1875em 0.3125em #33C9E8, 0 0 0.9375em white, 0 0.3125em 0.5em #38C1D2 inset; will-change: transform; } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container { height: 100%; position: relative; overflow: hidden; border-radius: 50%; -webkit-transform: rotate(-35deg); transform: rotate(-35deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella, .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .color-sector { position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: inherit; } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella { -webkit-clip-path: inset(0 0 50% 50%); -moz-clip-path: inset(0 0 50% 50%); clip-path: inset(0 0 50% 50%); overflow: hidden; -webkit-filter: blur(30px); -moz-filter: blur(30px); filter: blur(30px); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector { -webkit-clip-path: inset(0 50% 0 0); -moz-clip-path: inset(0 50% 0 0); clip-path: inset(0 50% 0 0); background-color: rgba(19, 182, 206, 0.2); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(1) { -webkit-transform: rotate(60deg); transform: rotate(60deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(2) { -webkit-transform: rotate(54deg); transform: rotate(54deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(3) { -webkit-transform: rotate(48deg); transform: rotate(48deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(4) { -webkit-transform: rotate(42deg); transform: rotate(42deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(5) { -webkit-transform: rotate(36deg); transform: rotate(36deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(6) { -webkit-transform: rotate(30deg); transform: rotate(30deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(7) { -webkit-transform: rotate(24deg); transform: rotate(24deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(8) { -webkit-transform: rotate(18deg); transform: rotate(18deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(9) { -webkit-transform: rotate(12deg); transform: rotate(12deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .umbrella .color-sector:nth-child(10) { -webkit-transform: rotate(6deg); transform: rotate(6deg); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration { position: relative; height: 100%; } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .thin-border, .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .small-ellipse { -webkit-filter: blur(2px); margin-left: auto; margin-right: auto; } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .thin-border { width: 0.0625em; height: 50%; background-color: rgba(65, 191, 226, 0.5); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .small-ellipse { background-color: white; border-radius: 50%; position: absolute; left: 0; right: 0; -webkit-transform: translateZ(0); transform: translateZ(0); } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .small-ellipse:nth-child(2) { width: 0.9375em; height: 0.1875em; top: 3%; } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .small-ellipse:nth-child(3) { width: 1.375em; height: 0.25em; top: 17%; } .radar .scanning-circle .radar-scanner .outer-scanner .scanner-container .scanner-decoration .small-ellipse:nth-child(4) { width: 1em; height: 0.125em; top: 33%; opacity: 0.3; } .radar .scanning-circle .scanning-dashed-circle { -webkit-animation: rotation 2.67s linear infinite; animation: rotation 2.67s linear infinite; } .radar .scanning-circle .scanning-dashed-empty-circle { -webkit-animation: rotation 1.67s linear infinite; animation: rotation 1.67s linear infinite; } .radar.lying-down .radar-map-container, .radar.lying-down .radar-map, .radar.lying-down .scanning-circle, .radar.lying-down .risk-elements-group { -webkit-transition-duration: 1s; transition-duration: 1s; } .radar.lying-down .radar-map-container { -webkit-transform: scale(1); transform: scale(1); } .radar.lying-down .radar-map { -webkit-transform: translateZ(-125em) rotateX(70deg); transform: translateZ(-125em) rotateX(70deg); color: transparent; } .radar.lying-down .radar-map .risk-elements-group { opacity: 1; } .radar.lying-down .radar-map .risk-elements-group .info-panel { opacity: .4; } .radar.lying-down .radar-map .risk-elements-group .red-flag { -webkit-transition: opacity 0.5s linear, visibility, -webkit-transform .3s linear; transition: opacity 0.5s linear, visibility, -webkit-transform .3s linear; transition: transform .3s linear, opacity 0.5s linear, visibility; transition: transform .3s linear, opacity 0.5s linear, visibility, -webkit-transform .3s linear; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(19) .red-flag { -webkit-transition-delay: 5.7s, 0s, 5.7s; transition-delay: 5.7s, 0s, 5.7s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(19) .dashed-circle { -webkit-transition-delay: 5.7s; transition-delay: 5.7s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(18) .red-flag { -webkit-transition-delay: 5.3999999999999995s, 0s, 5.3999999999999995s; transition-delay: 5.3999999999999995s, 0s, 5.3999999999999995s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(18) .dashed-circle { -webkit-transition-delay: 5.3999999999999995s; transition-delay: 5.3999999999999995s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(17) .red-flag { -webkit-transition-delay: 5.1s, 0s, 5.1s; transition-delay: 5.1s, 0s, 5.1s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(17) .dashed-circle { -webkit-transition-delay: 5.1s; transition-delay: 5.1s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(16) .red-flag { -webkit-transition-delay: 4.8s, 0s, 4.8s; transition-delay: 4.8s, 0s, 4.8s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(16) .dashed-circle { -webkit-transition-delay: 4.8s; transition-delay: 4.8s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(15) .red-flag { -webkit-transition-delay: 4.5s, 0s, 4.5s; transition-delay: 4.5s, 0s, 4.5s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(15) .dashed-circle { -webkit-transition-delay: 4.5s; transition-delay: 4.5s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(14) .red-flag { -webkit-transition-delay: 4.2s, 0s, 4.2s; transition-delay: 4.2s, 0s, 4.2s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(14) .dashed-circle { -webkit-transition-delay: 4.2s; transition-delay: 4.2s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(13) .red-flag { -webkit-transition-delay: 3.9s, 0s, 3.9s; transition-delay: 3.9s, 0s, 3.9s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(13) .dashed-circle { -webkit-transition-delay: 3.9s; transition-delay: 3.9s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(12) .red-flag { -webkit-transition-delay: 3.5999999999999996s, 0s, 3.5999999999999996s; transition-delay: 3.5999999999999996s, 0s, 3.5999999999999996s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(12) .dashed-circle { -webkit-transition-delay: 3.5999999999999996s; transition-delay: 3.5999999999999996s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(11) .red-flag { -webkit-transition-delay: 3.3s, 0s, 3.3s; transition-delay: 3.3s, 0s, 3.3s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(11) .dashed-circle { -webkit-transition-delay: 3.3s; transition-delay: 3.3s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(10) .red-flag { -webkit-transition-delay: 3s, 0s, 3s; transition-delay: 3s, 0s, 3s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(10) .dashed-circle { -webkit-transition-delay: 3s; transition-delay: 3s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(9) .red-flag { -webkit-transition-delay: 2.6999999999999997s, 0s, 2.6999999999999997s; transition-delay: 2.6999999999999997s, 0s, 2.6999999999999997s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(9) .dashed-circle { -webkit-transition-delay: 2.6999999999999997s; transition-delay: 2.6999999999999997s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(8) .red-flag { -webkit-transition-delay: 2.4s, 0s, 2.4s; transition-delay: 2.4s, 0s, 2.4s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(8) .dashed-circle { -webkit-transition-delay: 2.4s; transition-delay: 2.4s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(7) .red-flag { -webkit-transition-delay: 2.1s, 0s, 2.1s; transition-delay: 2.1s, 0s, 2.1s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(7) .dashed-circle { -webkit-transition-delay: 2.1s; transition-delay: 2.1s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(6) .red-flag { -webkit-transition-delay: 1.7999999999999998s, 0s, 1.7999999999999998s; transition-delay: 1.7999999999999998s, 0s, 1.7999999999999998s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(6) .dashed-circle { -webkit-transition-delay: 1.7999999999999998s; transition-delay: 1.7999999999999998s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(5) .red-flag { -webkit-transition-delay: 1.5s, 0s, 1.5s; transition-delay: 1.5s, 0s, 1.5s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(5) .dashed-circle { -webkit-transition-delay: 1.5s; transition-delay: 1.5s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(4) .red-flag { -webkit-transition-delay: 1.2s, 0s, 1.2s; transition-delay: 1.2s, 0s, 1.2s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(4) .dashed-circle { -webkit-transition-delay: 1.2s; transition-delay: 1.2s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(3) .red-flag { -webkit-transition-delay: 0.8999999999999999s, 0s, 0.8999999999999999s; transition-delay: 0.8999999999999999s, 0s, 0.8999999999999999s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(3) .dashed-circle { -webkit-transition-delay: 0.8999999999999999s; transition-delay: 0.8999999999999999s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(2) .red-flag { -webkit-transition-delay: 0.6s, 0s, 0.6s; transition-delay: 0.6s, 0s, 0.6s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(2) .dashed-circle { -webkit-transition-delay: 0.6s; transition-delay: 0.6s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(1) .red-flag { -webkit-transition-delay: 0.3s, 0s, 0.3s; transition-delay: 0.3s, 0s, 0.3s; } .radar.lying-down .radar-map:not(.roaming) .risk-elements-group:nth-child(1) .dashed-circle { -webkit-transition-delay: 0.3s; transition-delay: 0.3s; } .radar.pause-animation .inner-scanner, .radar.pause-animation .scanning-dashed-circle, .radar.pause-animation .scanning-dashed-empty-circle { -webkit-animation-play-state: paused; animation-play-state: paused; } .radar.pause-animation .changing-number-container { display: none; } .radar.pause-animation .risk-points { display: none; } .radar.pause-animation .scanning-circle { -webkit-transform: scale(0); transform: scale(0); opacity: 0; } html, body { width: 100%; height: 100%; } body { font-size: 0.8em; margin: 0; background-color: black; } .radar { left: 0; right: 0; top: 0; bottom: 0; margin: auto; } #radar-switch-btn { position: absolute; left: 0; top: 0; color: white; } @media only screen and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) { .radar { overflow: hidden; } }
JavaScript代码
JS代码主要是实现向雷达div容器中注入其他元素以及3D地图中地点标注信息的切换展示,代码如下:
function Radar(radarContainer) {
radarContainer.innerHTML = '<div class="radar-map-container">\
<div class="radar-map"></div>\
</div>\
<div class="changing-number-container" data-number="1234567890456"></div>\
<div class="risk-points"></div>\
<div class="scanning-circle">\
<div class="radar-scanner">\
<div class="inner-scanner"></div>\
<div class="outer-scanner">\
<div class="scanner-container">\
<div class="umbrella"></div>\
<div class="scanner-decoration">\
<div class="thin-border"></div>\
<div class="small-ellipse"></div>\
<div class="small-ellipse"></div>\
<div class="small-ellipse"></div>\
</div>\
</div>\
</div>\
</div>\
</div>';
this._container = radarContainer;
this._containerWidth = this._container.offsetWidth;
this._containerHeight = this._container.offsetHeight;
this._centerX = this._containerWidth / 2;
this._centerY = this._containerHeight / 2;
// 跟风险点"蒙版"有关的变量
this._maskCtx = (function(cw, ch) {
var c = document.createElement('canvas');
c.width = cw;
c.height = ch;
return c.getContext('2d');
})(this._containerWidth, this._containerHeight);
this._maskSectorDegree = 60; // 雷达扇形所占角度
this._maskStartDegree = 0; // 雷达扇形开始扫描的角度
this._scanSpeed = 2; // 雷达扫描速度,单位为deg
this._outerScanner = this._container.querySelector('.outer-scanner'); // 外层雷达扫描器
this._riskPointsContainer = this._container.querySelector('.risk-points');
this._allRiskPointsArr = []; // 保存所有风险点的数组,改变雷达扫描速度时要用到这些信息
this._tmpRiskPointsArr = []; // 初始化时保存所有的风险点,雷达扫描过程中会遍历这个数组,并把当前扫描到的风险点从这个数组移动到保存风险点的对象中,雷达扫描玩一遍之后这个数组为空。这样做避免雷达扫描时重复遍历风险点,特别是当有很多风险点的时候
this._riskPoints = {}; // 以扫描角度为键值保存风险点信息
this._riskElements = []; // 与风险点相关的虚线圆圈、红旗、信息面面和位置信息
this._curRoamingIndex = 0; // 当前漫游的风险点索引
this._radarMap = this._container.querySelector('.radar-map');
this._roamingDuration = 0; // 两个风险点之间漫游时长
this._changingNumberContainer = this._container.querySelector('.changing-number-container'); // 不断变化的数字
this._digits_base = Math.pow(10, Math.min(this._changingNumberContainer.dataset.number.length, 16)); // 数字位数,最大为16
this._mapTranslateZ = (function(container) { // 相机漫游时拉近的距离
var fontSize = parseInt(getComputedStyle(container).fontSize);
return 300 / 16 * fontSize;
})(this._container);
this._scaleFactor = this._radarMap.offsetWidth / this._containerWidth; // 为了使地图拉近之后不失真而放大的倍数
this._rotateX = 70; // 地图倒下旋转的角度
}
/* 外部调用的方法 */
Radar.prototype.init = function(options) {
/*
options = {
scanSpeed: 2 // 扫描的速度,单位为deg
}
*/
options.scanSpeed && (this._scanSpeed = parseInt(options.scanSpeed));
this._createCanvasElements();
this._drawLineAndCircle();
this._drawDashedCircle();
this._drawDashedEmptyCircle();
this._drawScannerSector();
this._animate();
this._initEvent();
};
// 添加风险点
Radar.prototype.addRiskPoints = function(points) {
if(!(points instanceof Array)) {
points = [points];
}
points.forEach(this._addOneRiskPoint.bind(this));
};
// 一次设置多个风险点
Radar.prototype.setRiskPoints = function(points) {
this._removeAllRiskPoints();
this.addRiskPoints(points);
};
// 调整雷达扫描速度
Radar.prototype.changeScanSpeed = function(perTimeDeg) {
perTimeDeg = parseInt(perTimeDeg);
if(perTimeDeg == this._scanSpeed || 360 % perTimeDeg != 0) { // 每次旋转的角度必须是360的约数,否则可能会出现跳过风险点的情况
return;
}
this._riskPoints = {};
this._tmpRiskPointsArr = this._allRiskPointsArr;
this._scanSpeed = perTimeDeg;
};
// 雷达状态切换
Radar.prototype.roamingToggle = function() {
this._container.classList.toggle('lying-down');
if(this._isLyingDown()) {
// 倒下之后停止动画绘制
this._pauseAnimation();
} else {
this._radarMap.classList.remove('roaming');
this._radarMap.style.removeProperty('transform');
}
};
Radar.prototype.startRoaming = function() {
this._container.classList.add('lying-down');
this._pauseAnimation();
};
Radar.prototype.stopRoaming = function() {
this._container.classList.remove('lying-down');
this._radarMap.classList.remove('roaming');
this._radarMap.style.removeProperty('transform');
};
/* 私有方法 */
Radar.prototype._addOneRiskPoint = function(options) {
/**
options = {
type: 'type1', // 'type1' 或者 'type2',风险点的颜色是红色还是蓝色
severity: 'critical', // 风险严重程度,'ordinary'表示普通,'critical'表示严重
coords: [134.7728099, 53.56097399999999], // 城市的经纬度
city: '北京',
title: '大额预授权交易异常',
total: 3 // 风险卡的数量
}
**/
// 计算风险点屏幕坐标
var pointCoords = this._geoCoordsToScreenCoords(options.coords),
point_x = pointCoords[0],
point_y = pointCoords[1];
/*// 计算风险点索引
var riskPointIndex = this._calcRiskPointIndex(point_x, point_y);
if(!this._riskPoints[riskPointIndex]) {
var riskPointGroup = document.createElement('div'); // 相同索引的风险点放在一组
riskPointGroup.className = 'risk-point-group';
this._riskPointsContainer.appendChild(riskPointGroup);
this._riskPoints[riskPointIndex] = riskPointGroup;
}*/
// 创建风险点元素
var riskPoint = document.createElement('div');
riskPoint.className = 'risk-point ' + options.type + ' ' + options.severity;
if(options.type == 'pulsation') {
riskPoint.innerHTML = '<div class="pulse-circle"></div>\
<div class="pulse-circle"></div>\
<div class="pulse-circle"></div>';
}
//this._riskPoints[riskPointIndex].appendChild(riskPoint);
// 计算并设置风险点位置
var point_left = point_x - riskPoint.offsetWidth / 2,
point_top = point_y - riskPoint.offsetHeight / 2;
riskPoint.style.cssText = 'left: ' + point_left + 'px; top: ' + point_top + 'px;';
var riskPointItem = {
x: point_x,
y: point_y,
target: riskPoint
};
this._allRiskPointsArr.push(riskPointItem);
this._tmpRiskPointsArr.push(riskPointItem);
// 创建跟风险点相关的红旗、虚线圆圈和信息面板
var elements_group = document.createElement('div');
elements_group.className = 'risk-elements-group';
elements_group.innerHTML = '<div class="dashed-circle"></div>\
<div class="red-flag" data-city="' + options.city + '"></div>\
<div class="info-panel">\
<div class="info-title">' + options.title + '</div>\
<div class="info-content">\
<div>\
风险发生地:<span class="risk-city">' + options.city + '</span>\
</div>\
<div>\
风险卡片:<span class="risk-num">' + options.total + '</span>\
</div>\
</div>\
</div>';
this._radarMap.appendChild(elements_group);
var dashed_circle = elements_group.querySelector('.dashed-circle'),
red_flag = elements_group.querySelector('.red-flag'),
info_panel = elements_group.querySelector('.info-panel');
dashed_circle.style.backgroundImage = 'url(' + this._getDashedCircleBg(dashed_circle.offsetWidth) + ')';
// 计算和设置圆圈、红旗、信息面板的位置
var dashed_circle_left = point_x * this._scaleFactor - dashed_circle.offsetWidth / 2,
dashed_circle_top = point_y * this._scaleFactor - dashed_circle.offsetHeight / 2,
red_flag_left = point_x * this._scaleFactor - red_flag.offsetWidth / 2,
red_flag_top = point_y * this._scaleFactor - red_flag.offsetHeight,
info_panel_left = point_x * this._scaleFactor - info_panel.offsetWidth / 2,
info_panel_top = point_y * this._scaleFactor - info_panel.offsetHeight;
dashed_circle.style.left = dashed_circle_left + 'px';
dashed_circle.style.top = dashed_circle_top + 'px';
if(this._isLyingDown()) {
dashed_circle.style.visibility = 'visible';
}
red_flag.style.left = red_flag_left + 'px';
red_flag.style.top = red_flag_top + 'px';
info_panel.style.left = info_panel_left + 'px';
info_panel.style.top = info_panel_top + 'px';
// 保存与风险点相关的元素
this._riskElements.push({
name: options.city,
riskPoint: riskPoint,
dashedCircle: dashed_circle,
redFlag: red_flag,
infoPanel: info_panel,
translate: [(this._containerWidth * 0.5 - point_x) * this._scaleFactor, this._calcTYByTZ(this._mapTranslateZ, dashed_circle.offsetHeight/2) - point_y * this._scaleFactor],
rotateY: (function(riskElements) {
// 旋转的角度正负零交替
var base_deg = 10,
max_deviation_deg = 5;
var pn = (riskElements.length % 2) * 2 - 1; // positive or negative
var new_base_deg = Math.ceil((riskElements.length + 1) % 3 / 2) * base_deg; // 10 10 0 10 10 0 ...
return pn * (new_base_deg + Math.round(Math.random() * max_deviation_deg));
})(this._riskElements),
transformOrigin: Math.round(point_x / this._containerWidth * 100) + '%'
});
// 旋转信息面板,使信息面板转到眼前时正对镜头
var rotate_deg = this._riskElements[this._riskElements.length - 1].rotateY;
info_panel.style.transform = getComputedStyle(info_panel).transform + ' rotateY(' + (-rotate_deg) + 'deg)';
};
// 根据名称删除风险点
Radar.prototype._removeRiskPointByName = function(name) {
for(var i = 0, len = this._riskElements.length; i < len; i++) {
var curRiskElement = this._riskElements[i];
if(curRiskElement.name == name) {
var riskPointGroup = curRiskElement.riskPoint.parentElement,
riskElementsGroup = curRiskElement.dashedCircle.parentElement;
riskPointGroup.removeChild(curRiskElement.riskPoint);
this._radarMap.removeChild(riskElementsGroup);
this._riskElements.splice(i, 1);
return true;
}
}
return false;
};
// 删除所有风险点
Radar.prototype._removeAllRiskPoints = function() {
var total = this._riskElements.length;
this._radarMap.innerHTML = '';
this._riskPointsContainer.innerHTML = '';
this._riskPoints = {};
this._riskElements = [];
return total;
};
// 暂停动画
Radar.prototype._pauseAnimation = function() {
cancelAnimationFrame(this._requestId);
this._container.classList.add('pause-animation');
};
// 恢复动画
Radar.prototype._resumeAnimation = function() {
this._requestId = requestAnimationFrame(this._animate.bind(this));
this._container.classList.remove('pause-animation');
};
// 创建canvas标签
Radar.prototype._createCanvasElements = function() {
var scanningCircleElement = this._container.querySelector('.scanning-circle');
// 绘制雷达静止的线框和圆圈用到的canvas
var canvas = document.createElement('canvas');
canvas.width = this._containerWidth;
canvas.height = this._containerHeight;
scanningCircleElement.appendChild(canvas);
this._lineAndCircleCanvas = canvas;
// 绘制内部旋转的 "虚线" 圆圈用到的canvas
this._dashedCircleCanvas = canvas.cloneNode(true);
this._dashedCircleCanvas.className = 'scanning-dashed-circle';
scanningCircleElement.appendChild(this._dashedCircleCanvas);
// 绘制内部旋转的 "空心虚线" 圆圈用到的canvas
this._dashedEmptyCircleCanvas = canvas.cloneNode(true);
this._dashedEmptyCircleCanvas.className = 'scanning-dashed-empty-circle';
scanningCircleElement.appendChild(this._dashedEmptyCircleCanvas);
};
// 地理坐标转换成屏幕坐标
Radar.prototype._geoCoordsToScreenCoords = function(geoCoords) { // geoCoords:经纬度数组
var china_geometry_bounds = [73.4994136, 53.56097399999999, 61.2733963, 35.40335839999999]; // 西北的经纬度和经纬度跨度
var point_x = Math.abs(geoCoords[0] - china_geometry_bounds[0]) / china_geometry_bounds[2] * this._containerWidth,
point_y = Math.abs(geoCoords[1] - china_geometry_bounds[1]) / china_geometry_bounds[3] * this._containerHeight;
return [point_x, point_y];
};
// 计算风险点的索引(雷达扇形扫描到多少度时要显示这个风险点)
Radar.prototype._calcRiskPointIndex = function(point_x, point_y) {
var point_offset_x = point_x - this._centerX, // 与中心点的偏移
point_offset_y = this._centerY - point_y,
riskPointRadian = -Math.atan(point_offset_y / point_offset_x); // 风险点在雷达扫描圆形的弧度
if(point_offset_x < 0) {
riskPointRadian -= Math.PI;
} else if(point_offset_y < 0) {
riskPointRadian -= Math.PI * 2;
}
var riskPointDeg = 180 / Math.PI * riskPointRadian;
var riskPointIndex = '_deg_' + riskPointDeg.toFixed();
return riskPointIndex;
};
// 根据地图倒下之后translateZ的大小计算风险点应该拉近到相机前面的y值,使得风险点向镜头拉近后正好在屏幕正下方
Radar.prototype._calcTYByTZ = function(tz, dashed_circle_r) {
var p = parseInt(getComputedStyle(this._container).perspective), // 原始视角
pr = (p - tz) / p, // 应用translateZ后的视角比例
point_screen_bottom_offset = dashed_circle_r / pr, // 风险点漫游时要平移到的位置与屏幕底部的偏移量
tr_o_offset_y = pr * (document.body.clientHeight - this._containerHeight / 2 - this._container.getBoundingClientRect().top - point_screen_bottom_offset); // 最后相机要拉近到的y值与transform origin中心点的偏移量
return this._radarMap.offsetHeight / 2 + tr_o_offset_y;
};
/*Radar.prototype._calcTYByTZ = function(tz, dashed_circle_r) {
var p = parseInt(getComputedStyle(this._container).perspective), // 原始视角
pr = (p - tz) / p, // 应用translateZ后的视角比例
tmp_tr_o_offset_y = document.body.clientHeight - this._containerHeight / 2 - this._container.getBoundingClientRect().top; // 最后相机要拉近到的y值与transform origin中心点的偏移量
var tr_o_offset_y = this._calcOffsetBeforeRotateX(tmp_tr_o_offset_y, this._rotateX, p-tz);
return this._radarMap.offsetHeight / 2 + tr_o_offset_y * pr;
};*/
// 根据绕x轴旋转之后风险点的位置计算旋转之前风险点要平移到的位置
Radar.prototype._calcOffsetBeforeRotateX = function(newOffset, rotateX, oldPerspective) { // 计算不正确
// (newOffset / cos(rotateX)) / x = oldPerspective / (oldPerspective - x * sin(rotateX))
var x1 = newOffset / Math.cos(Math.PI / 180 * rotateX);
return oldPerspective * x1 / (oldPerspective + x1 * Math.sin(Math.PI / 180 * rotateX));
};
// 动画
Radar.prototype._animate = function() {
this._rotateRiskPointMask();
this._changeNumber();
this._requestId = requestAnimationFrame(arguments.callee.bind(this));
};
// 变化数字
Radar.prototype._changeNumber = function() {
var _assist_number = arguments.callee._assist_number || 0;
if(_assist_number % 6 == 0) {
var number = Math.round(Math.random() * this._digits_base);
this._changingNumberContainer.dataset.number = number;
}
arguments.callee._assist_number = (++_assist_number) % 360;
};
// 绘制雷达静止的线框和圆圈
Radar.prototype._drawLineAndCircle = function() {
var radius = this._containerHeight / 2,
ctx = this._lineAndCircleCanvas.getContext('2d');
// 最外层圆圈
var lineWidth = 5;
ctx.lineWidth = lineWidth;
ctx.strokeStyle = '#0146C2';
ctx.beginPath();
ctx.arc(this._centerX, this._centerY, radius - lineWidth / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
// 内部圆圈
ctx.fillStyle = 'rgba(30,199,230,.5)';
ctx.beginPath();
ctx.arc(this._centerX, this._centerY, 3, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
var totalCircle = 8;
ctx.lineWidth = 0.5;
ctx.strokeStyle = 'rgba(30,199,230,.5)';
for(var i = 0; i < totalCircle - 1; i++) {
ctx.beginPath();
ctx.arc(this._centerX, this._centerY, radius / totalCircle * (i + 1), 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
}
// 内部直线
var totalLines = 14;
ctx.save();
ctx.lineWidth = 0.3;
ctx.translate(this._centerX, this._centerY);
ctx.rotate(Math.PI / totalLines);
for(var i = 0; i < totalLines; i++) {
ctx.rotate(Math.PI * 2 / totalLines);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -radius + lineWidth);
ctx.closePath();
ctx.stroke();
}
ctx.restore();
// 内部虚线
ctx.save();
ctx.setLineDash([2, 8]);
ctx.translate(this._centerX, this._centerY);
for(var i = 0; i < totalLines / 2; i++) {
ctx.rotate(Math.PI * 4 / totalLines);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -radius + lineWidth);
ctx.closePath();
ctx.stroke();
}
ctx.restore();
};
// 绘制内部旋转的 "虚线" 圆圈
Radar.prototype._drawDashedCircle = function() {
var ctx = this._dashedCircleCanvas.getContext('2d');
ctx.clearRect(-this._centerX, -this._centerY, this._dashedCircleCanvas.width, this._dashedCircleCanvas.height);
ctx.globalAlpha = 0.8;
ctx.lineWidth = 5;
ctx.translate(this._centerX, this._centerY);
var radius = this._containerHeight / 2 / 8 * 7 - ctx.lineWidth / 2;
ctx.strokeStyle = '#016FB7';
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 5);
ctx.stroke();
ctx.strokeStyle = '#23B2D8';
ctx.rotate(Math.PI / 3);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 6);
ctx.stroke();
ctx.strokeStyle = '#80DEF9';
ctx.rotate(Math.PI / 3);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 18);
ctx.stroke();
ctx.strokeStyle = '#085BAF';
ctx.rotate(Math.PI / 4);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 9);
ctx.stroke();
};
// 绘制内部旋转的空心虚线圆圈
Radar.prototype._drawDashedEmptyCircle = function() {
var ctx = this._dashedEmptyCircleCanvas.getContext('2d');
ctx.clearRect(-this._centerX, -this._centerY, this._dashedEmptyCircleCanvas.width, this._dashedEmptyCircleCanvas.height);
ctx.strokeStyle = '#5298D3';
ctx.globalAlpha = 0.3;
ctx.translate(this._centerX, this._centerY);
var radius = this._containerHeight / 2 / 8 * 6,
radiusDeviation = 5, // 空心虚线的高度
innerRadius = radius - radiusDeviation;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 5);
ctx.arc(0, 0, innerRadius, Math.PI / 5, 0, true);
ctx.closePath();
ctx.stroke();
ctx.rotate(Math.PI / 3);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 6);
ctx.arc(0, 0, innerRadius, Math.PI / 6, 0, true);
ctx.closePath();
ctx.stroke();
ctx.rotate(Math.PI / 3);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 18);
ctx.arc(0, 0, innerRadius, Math.PI / 18, 0, true);
ctx.closePath();
ctx.stroke();
ctx.rotate(Math.PI / 4);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI / 9);
ctx.arc(0, 0, innerRadius, Math.PI / 9, 0, true);
ctx.closePath();
ctx.stroke();
};
// 绘制雷达扫描锥形渐变扇形
Radar.prototype._drawScannerSector = function() {
// 创建canvas元素
var c = document.createElement('canvas');
c.width = c.height = this._containerHeight;
this._outerScanner.querySelector('.umbrella').appendChild(c);
// 绘制锥形渐变
var ctx = c.getContext('2d');
var sectorCnt = 10; // 用10块扇形模拟锥形渐变
var startDeg = -90, endDeg;
var sectorRadius = this._containerHeight / 2;
ctx.translate(sectorRadius, sectorRadius);
ctx.fillStyle = 'rgba(19, 182, 206, 0.2)';
for(var i = 0; i < sectorCnt; i++) {
endDeg = startDeg + this._maskSectorDegree - this._maskSectorDegree / sectorCnt * i;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, sectorRadius);
ctx.arc(0, 0, sectorRadius, Math.PI / 180 * (startDeg - 180), Math.PI / 180 * endDeg);
ctx.closePath();
ctx.fill();
}
};
// 旋转只显示风险点的区域
Radar.prototype._rotateRiskPointMask = function() {
function angleToRadian(angle) {
return Math.PI / 180 * angle;
}
this._maskStartDegree = this._maskStartDegree % 360;
this._maskCtx.beginPath();
this._maskCtx.moveTo(this._centerX, this._centerY);
this._maskCtx.arc(this._centerX, this._centerY, this._containerHeight / 2, angleToRadian(this._maskStartDegree), angleToRadian(this._maskStartDegree + this._maskSectorDegree));
this._maskCtx.lineTo(this._centerX, this._centerY);
this._maskCtx.closePath();
// 控制风险点的显示
var riskPointIndex = '_deg_' + this._maskStartDegree;
if(!this._riskPoints[riskPointIndex] && this._tmpRiskPointsArr.length > 0) {
// todo: 这里先判断this._riskPoints[riskPointIndex]可能会出现不去处理this._tmpRiskPointsArr的情况,特别是当风险点在某一块区域很密集的时候,而且后面添加的风险点都会另开一组
var that = this;
this._tmpRiskPointsArr.forEach(function(point) {
if(that._maskCtx.isPointInPath(point.x, point.y)) {
// 把当前扫描到的风险点缓存起来
if(!that._riskPoints[riskPointIndex]) {
var riskPointGroup = document.createElement('div'); // 相同索引的风险点放在一组
riskPointGroup.className = 'risk-point-group';
that._riskPointsContainer.appendChild(riskPointGroup);
that._riskPoints[riskPointIndex] = riskPointGroup;
}
that._riskPoints[riskPointIndex].appendChild(point.target);
point._willDelete = true; // 将要删除的标记
}
});
// 遍历完之后删除已处理过的风险点
this._tmpRiskPointsArr = this._tmpRiskPointsArr.filter(function(pointItem) {
var flag = !pointItem._willDelete;
delete pointItem._willDelete;
return flag;
});
}
this._riskPoints[riskPointIndex] && this._riskPoints[riskPointIndex].classList.add('flashing');
// 旋转雷达扫描扇形
this._outerScanner.style.transform = 'rotate(' + this._maskStartDegree + 'deg) translateZ(0)';
this._maskStartDegree -= this._scanSpeed;
};
// 绘制风险点旋转的虚线圆圈背景
Radar.prototype._getDashedCircleBg = function(radius) {
var center_x = radius / 2,
center_y = radius / 2;
var c = document.createElement('canvas');
c.width = radius;
c.height = radius;
var ctx = c.getContext('2d');
ctx.strokeStyle = '#EAFF00';
ctx.shadowColor = '#EAFF00';
ctx.shadowBlur = radius / 25;
// 绘制内圆环
ctx.lineWidth = radius / 50;
ctx.beginPath();
ctx.arc(center_x, center_y, radius/4, 0, Math.PI*2);
ctx.stroke();
// 绘制外层虚线圆环
var dash_cnt = 5, // 实心虚线点个数
dash_ratio = 0.8; // 空心虚线点与实心虚线点的比例
ctx.lineWidth = radius / 10;
var solid_dash_len = Math.PI * (radius - ctx.lineWidth) / dash_cnt / (1 + dash_ratio),
hollow_dash_len = solid_dash_len * dash_ratio;
ctx.setLineDash([solid_dash_len, hollow_dash_len]);
ctx.beginPath();
ctx.arc(center_x, center_y, radius/2-ctx.lineWidth/2, 0, Math.PI*2);
ctx.stroke();
return c.toDataURL();
};
// 地图倒下之后开始显示风险点处的虚线圆圈、红旗和信息面板
Radar.prototype._showRiskElements = function() {
this._riskElements.forEach(function(re, ri) {
re.dashedCircle.style.visibility = 'visible';
re.redFlag.classList.add('stand-up');
});
};
// 相机漫游到当前风险点时让红旗消失、让信息面板出现
Radar.prototype._handleCurRiskElements = function() {
var curRiskElements = this._riskElements[this._curRoamingIndex];
if(curRiskElements) {
curRiskElements.redFlag.style.opacity = 0;
curRiskElements.dashedCircle.style.visibility = 'visible';
curRiskElements.infoPanel.classList.add('showup');
curRiskElements.infoPanel.classList.add('polish');
// 设置上一个漫游的风险点的信息面板的透明度
var lastRiskElements = this._riskElements[(this._curRoamingIndex - 1 + this._riskElements.length) % this._riskElements.length];
if(lastRiskElements) {
lastRiskElements.infoPanel.style.removeProperty('opacity');
}
}
};
// 地图回到初始状态时隐藏跟风险点相关的元素
Radar.prototype._hideRiskElements = function() {
this._curRoamingIndex = 0;
this._riskElements.forEach(function(re, ri) {
re.redFlag.classList.remove('stand-up');
re.redFlag.style.opacity = 1;
re.dashedCircle.style.visibility = 'hidden';
re.infoPanel.style.removeProperty('opacity');
re.infoPanel.classList.remove('showup');
re.infoPanel.classList.remove('polish');
});
};
// 风险点镜头漫游
Radar.prototype._riskPointsRoaming = function() {
if(!this._isInRoamingState()) return;
var curRiskElements = this._riskElements[this._curRoamingIndex];
if(!curRiskElements) return;
var radarMapStyle = 'translateZ(' + this._mapTranslateZ + 'px) rotateY(' + curRiskElements.rotateY + 'deg) rotateX(' + this._rotateX + 'deg) translate3d(' + curRiskElements.translate[0] + 'px, ' + curRiskElements.translate[1] + 'px, 0)';
//test
//radarMapStyle = 'translateZ(' + this._mapTranslateZ + 'px) translate3d(' + curRiskElements.translate[0] + 'px, ' + curRiskElements.translate[1] + 'px, 0)';
//test
// 以上一个风险点为中心绕Y轴旋转(暂时以中心旋转)
/*var lastRiskElements = this._riskElements[(this._curRoamingIndex - 1 + this._riskElements.length) % this._riskElements.length];
this._radarMap.style.transformOrigin = lastRiskElements.transformOrigin;*/
this._radarMap.style.transform = radarMapStyle;
curRiskElements.infoPanel.classList.remove('polish');
// 当前的信息面板设置透明度
var roamingDuration = this._roamingDuration || parseFloat(getComputedStyle(this._radarMap).transitionDuration.split(', ')[0]);
setTimeout(function(that) {
if(that._isLyingDown()) {
curRiskElements.infoPanel.style.opacity = 1;
}
}, roamingDuration * 0.5 * 1000, this);
};
// 雷达是否是倒下的状态
Radar.prototype._isLyingDown = function() {
return this._container.classList.contains('lying-down');
};
// 雷达是否是漫游状态
Radar.prototype._isInRoamingState = function() {
return this._radarMap.classList.contains('roaming');
};
// 添加点击事件
Radar.prototype._initEvent = function() {
var that = this;
this._container.addEventListener('click', function(e) {
that.roamingToggle();
}, false);
this._riskPointsContainer.addEventListener('animationend', function(e) {
e.target.parentElement.classList.remove('flashing');
}, false);
this._radarMap.addEventListener('transitionend', function(e) {
if(e.propertyName != 'transform') return;
if(e.target === e.currentTarget) {
if(that._isLyingDown()) {
// 地图在倒下的状态
if(that._isInRoamingState()) {
// 相机漫游状态
that._handleCurRiskElements(); // 当前风险点漫游结束
that._curRoamingIndex = (that._curRoamingIndex + 1) % that._riskElements.length;
that._riskPointsRoaming(); // 下一个风险点漫游开始
} else {
// 刚刚倒下状态
that._showRiskElements();
}
} else {
that._hideRiskElements();
that._resumeAnimation();
}
} else if(e.target.classList.contains('red-flag') && !e.target.parentElement.nextElementSibling) {
// 最后一根红旗树立起来之后开始漫游
that._radarMap.classList.add('roaming'); // 修改transition duration和transition timing-function
setTimeout(function() {
that._riskPointsRoaming();
}, 1000);
}
}, false);
};
var radar = new Radar(document.querySelector('.radar'));
radar.init({scanSpeed: 2}); // 扫描的速度,单位为deg,必须为360的约数
radar.addRiskPoints([
{
type: 'type1',
severity: 'critical',
coords: [116.407395, 39.904211], // 北京
city: '北京',
title: '大额预授权交易异常',
total: 3
}, {
type: 'type1',
severity: 'ordinary',
coords: [104.066801, 30.572816], // 成都
city: '成都',
title: '大额预授权交易异常',
total: 3
}, {
type: 'type1',
severity: 'ordinary',
coords: [121.473701, 31.230416], // 上海
city: '上海',
title: '大额预授权交易异常',
total: 3
}, {
type: 'type2',
severity: 'ordinary',
coords: [113.264385, 23.12911], // 广州
city: '广州',
title: '大额预授权交易异常',
total: 3
}
]);
document.querySelector('#radar-switch-btn').addEventListener('click', e => {
radar.roamingToggle();
});
代码中注释比较详细,有兴趣的同学可以自己复制上面的的源码进行研究,HTML5和CSS3真是非常的强大。