css实现的一个雷达扫描效果,css真强大!

这几天有感于一个项目里的雷达扫描效果太差,不堪入目,于是自己尝试用css实现了一个类似的效果,现在贴出来,跟大家一起学习进步。
首先是封装过的前端雷达插件radar.js:

window.Radar = function (parent, cfg) {
    var radar = {
        parent: $(parent),
        defaultConfig: {
            frequency: 1,                          // 雷达波基础频率
            radius: '500px',                       // 默认扫描半径
            centerPoint: {x: '500px', y: '500px'}, // 默认中心坐标
            scanColor: '#00ff00',                  // 默认扫描效果颜色
            senterColor: '#00ff00'                 // 默认中心雷达效果颜色
            waveNum: 6,                            // 默认辐射波纹密度
            waveLlineWidth: '3px',                 // 默认辐射波纹线宽
            waveLineStyle: 'solid',                // 默认辐射波纹线型
            waveLineColor: '#00ff00',              // 默认辐射波纹线颜色
        },
        config: {},
        flyPoint: null,
        dom: null,
        isScan: false,
        scanInterval: null,
        hexToRgba: function(hex = '#000000', alpha = 1) {
            hex = hex.replace(/^#/, '');     

            alpha = alpha > 1 || alpha < 0 ? 1 : alpha;

            if (hex.length === 3) {
                hex = hex.split('').map(char => char + char).join('');
            }
            
            if (!/^[0-9A-F]{6}$/i.test(hex)) {
                throw new Error('Invalid hex color format');
            }
            
            const r = parseInt(hex.substring(0, 2), 16);
            const g = parseInt(hex.substring(2, 4), 16);
            const b = parseInt(hex.substring(4, 6), 16);                    
            return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
        },
        isScanOver: function(point, lineStart, lineEnd) {
            const {left: px, top: py} = point;
            const {left: x1, top: y1} = lineStart;
            const {left: x2, top: y2} = lineEnd;
            if ((x1 < x2 && (px < x1 || px > x2))
             || (y1 < y2 && (py < y1 || py > y2))
             || (x1 > x2 && (px > x1 || px < x2))
             || (y1 > y2 && (py > y1 || py < y2))) {
                return false;
            }
            
            const A = px - x1;
            const B = py - y1;
            const C = x2 - x1;
            const D = y2 - y1;
            
            const dot = A * C + B * D;
            const lenSq = C * C + D * D;
            
            let param = -1;
            if (lenSq !== 0) {
                param = dot / lenSq;
            }
            
            let xx, yy;
            
            if (param < 0) {
                xx = x1;
                yy = y1;
            } else if (param > 1) {
                xx = x2;
                yy = y2;
            } else {
                xx = x1 + param * C;
                yy = y1 + param * D;
            }
            
            const dx = px - xx;
            const dy = py - yy;
            
            return Math.sqrt(dx * dx + dy * dy) < 15;
        },
        setStyle: function() {
            var th = this;
            var cf = th.config;
            var css = ' <style type="text/css">'
                + ' /* 雷达扫描区域 */'
                + ' .radar-container {'
                + '     position: relative;'
                + '     background-color: transparent;'
                + '     overflow: hidden;'
                + '     width: '  + th.radius * 2 + 'px;'
                + '     height: ' + th.radius * 2 + 'px;'
                + '     padding: 0;'
                + '     margin: 0;'
                + '     top: ' + cf.top + 'px;'
                + '     left: ' + cf.left + 'px;'
                + '     border-radius: 50%;'
                + ' }'
                + ' /* 雷达原点 */'
                + ' .radar-center {'
                + '     position: absolute;'
                + '     top: ' + th.radius + 'px;'
                + '     left: ' + th.radius + 'px;'
                + '     transform: translate(-50%, -50%);'
                + '     width: 20px;'
                + '     height: 20px;'
                + '     background: radial-gradient(circle, ' + th.hexToRgba(cf.senterColor, 1) + ' 0%, ' + th.hexToRgba(cf.senterColor, 0.8) + ' 50%, ' + th.hexToRgba(cf.senterColor, 0.3) + ' 100%);'
                + '     border-radius: 50%;'
                + '     z-index: 10;'
                + '     animation: radar-pulse ' + (cf.frequency || 1) + 's ease-in-out infinite;'
                + ' }'
                + ' '
                + ' /* 动态扩散波纹 */'
                + ' .radar-wave {'
                + '     position: absolute;'
                + '     top: ' + th.radius + 'px;'
                + '     left: ' + th.radius + 'px;'
                + '     transform: translate(-50%, -50%);'
                + '     width: 20px;'
                + '     height: 20px;'
                + '     border: ' + cf.waveLlineWidth + ' ' + cf.waveLineStyle + ' ' + th.hexToRgba(cf.waveLineColor, 0.9) + ';'
                + '     border-radius: 50%;'
                + '     opacity: 0;'
                + '     animation: radar-wave ' + cf.waveNum + 's linear infinite;'
                + ' }'
                + ' '
                + ' /* 扫描扇区 */'
                + ' .radar-sector {'
                + '     position: relative;'
                + '     top: 0px;'
                + '     left: ' + th.radius + 'px;'
                + '     width: ' + th.radius + 'px;'
                + '     height: ' + th.radius * 2 + 'px;'
                + '     transform-origin: 0% 50%;'
                + '     background: conic-gradient('
                + '         from 0deg at 0% 50%,'
                + '         transparent 0deg,'
                + '         ' + th.hexToRgba(cf.scanColor, 0.0) + ' 0deg,'
                + '         ' + th.hexToRgba(cf.scanColor, 0.4) + ' 180deg,'
                + '         transparent 180deg'
                + '     );'
                + '     animation: radar-sector ' + (cf.frequency || 1) * 6 + 's linear infinite;'
                + '     z-index: 5;'
                + ' }'
                + ' /* 扫描线顶点坐标 */'
                + ' .radar-scan-top {'
                + '     position: relative;'
                + '     height: 1px;'
                + '     width: 1px;'
                + '     top:100%;'
                + '     left:0%;'
                + ' }'
                + ' /* 雷达原点缩放效果 */'
                + ' @keyframes radar-pulse {'
                + '     0%, 100% {'
                + '         box-shadow: 0 0 0 0 ' + th.hexToRgba(cf.senterColor, 0.4) + ';'
                + '         transform: translate(-50%, -50%) scale(1);'
                + '     }'
                + '     50% {'
                + '         box-shadow: 0 0 0 20px ' + th.hexToRgba(cf.senterColor, 0) + ';'
                + '         transform: translate(-50%, -50%) scale(1.1);'
                + '     }'
                + ' }'
                + ' '
                + ' /* 波纹效果 */'
                + ' @keyframes radar-wave {'
                + '     0% {'
                + '         width: 20px;'
                + '         height: 20px;'
                + '         opacity: 0.8;'
                + '     }'
                + '     100% {'
                + '         width: '  + th.radius * 2 + 'px;'
                + '         height: ' + th.radius * 2 + 'px;'
                + '         opacity: 0;'
                + '     }'
                + ' }'
                + ' /* 扫描效果 */'
                + ' @keyframes radar-sector {'
                + '     0% {'
                + '         transform: rotate(0deg);'
                + '     }'
                + '     100% {'
                + '         transform: rotate(360deg);'
                + '     }'
                + ' }'
                + ' /* 飞行点样式 */'
                + ' .fly-point {'
                + '     position: absolute;'
                + '     width: 20px;'
                + '     height: 20px;'
                + '     border-radius: 50%;' 
                + ' }'
                + ' /* 飞行点渐变效果 */'
                + ' .fly-point.fade-out {'
                + '     background-color: rgba(255, 255, 0, 0.1) !important;'
                + '     transition: background-color 3s ease;'
                + ' }'
                + 'tyle>';                
            $('head').append(css);
        },
        /* 扫描飞行点 */
        scan: function(){
            var th = this;
            var ctft = th.centerPointDom.offset();
            var txt = $('.flyPoint');
            th.scanInterval = setInterval(function(){
                if(th.flyPoint){
                    var dom  = th.flyPoint.dom;
                    if(dom){
                        var ptft = dom.offset();
                        var ltft = th.scanTopPointDom.offset();
                        if(!th.isScan){
                            if(th.isScanOver(ptft, ctft, ltft)){
                                th.isScan = true;
                                dom.css({backgroundColor: 'rgba(255, 255, 0, 0.8) '});
                                setTimeout(function(){
                                    dom[0].classList.add('fade-out');
                                }, 200);
                                setTimeout(function(){
                                    dom.css({backgroundColor: 'rgba(255, 255, 0, 0.1) '});
                                    dom[0].classList.remove('fade-out');
                                    th.isScan = false;
                                }, 3000);
                            }
                        }
                    }
                }
            }, 1);
        },
        /* 随机生成一个飞行点 
           本方法是为了产生一个从任意角度飞入的圆点,暂时没有想到好的算法,因此代码比较臃肿,
           希望大家能给出一个较好的算法实现。
         */
        randomFlyPoint: function(){ 
            var th = this;
            var cfg = th.config;
            var r1 = Math.random() * 4;
            var rdm1 = parseInt(Math.random() * 2 * th.radius, 10);
            var rdm2 = parseInt(Math.random() * (3 * th.radius - 200), 10) + 100;
            var points = {};
            if(r1 <= 1){
                points.start = {x: cfg.left, y: cfg.top + rdm1};
                points.end = rdm2 <= th.radius     ? {x: cfg.left + rdm2, y: cfg.top} 
                           : rdm2 <= 2 * th.radius ? {x: cfg.left + 2 * th.radius, y: cfg.top + rdm2 - th.radius}
                           :                         {x: cfg.left + rdm2 - 2 * th.radius + 100, y: cfg.top + 2 * th.radius};
            }else if(r1 <= 2){ 
                points.start = {x: cfg.left + rdm1, y: cfg.top};
                points.end = rdm2 <= th.radius     ? {x: cfg.left + 2 * th.radius, y: cfg.top + rdm2 - th.radius} 
                           : rdm2 <= 2 * th.radius ? {x: cfg.left + rdm2 - th.radius, y: cfg.top + 2 * th.radius}
                           :                         {x: cfg.left, y: cfg.top + rdm2 - 2 * th.radius + 100};
            }else if(r1 <= 3){ 
                points.start = {x: cfg.left + 2 * th.radius, y: cfg.top + rdm1};
                points.end = rdm2 <= th.radius     ? {x: cfg.left + rdm2 - 100,    y: cfg.top + 2 * th.radius} 
                           : rdm2 <= 2 * th.radius ? {x: cfg.left, y: cfg.top + rdm2 - th.radius}
                           :                         {x: cfg.left + rdm2 - 2 * th.radius - 100,    y: cfg.top};
            }else{
                points.start = {x: cfg.left + rdm1, y: cfg.top + 2 * th.radius};
                points.end = rdm2 <= th.radius     ? {x: cfg.left, y: cfg.top + rdm2 - 100} 
                           : rdm2 <= 2 * th.radius ? {x: cfg.left + rdm2 - th.radius, y: cfg.top}
                           :                         {x: cfg.left + 2 * th.radius, y: cfg.top + rdm2 - 2 * th.radius};
            }
            return {startPoint: points.start, endPoint: points.end, distince: {x: points.end.x - points.start.x, y: points.end.y - points.start.y}, time: 100, isScan: false};
        },
        /* 画面添加一个飞行点 */
        addFlyPoint: function(){
            var th = this;
            var flyPoint = th.randomFlyPoint();
            if(th.flyPoint){
                th.flyPoint.dom.remove();
                th.flyPoint = null;
            }
            var dm = flyPoint.dom = $('<div class="fly-point" style="left:' + flyPoint.startPoint.x + 'px;top:' + flyPoint.startPoint.y + 'px;"></div>').appendTo(th.dom);
            th.flyPoint = flyPoint;

            dm.animate({
                top: flyPoint.endPoint.y,
                left: flyPoint.endPoint.x
            }, flyPoint.time * 1000, 'linear', function(){
                dm.remove();
                th.flyPoint = null;
            });
        },

        /**
         * 实例化雷达
         */
        init: function(cfg){
            var th = this;
            // 合并配置
            th.config = Object.assign({}, th.defaultConfig, cfg);
            var cf = th.config;
            th.radius = parseInt(cf.radius, 10);

            // 计算雷达扫描区域起始点坐标
            cf.top = parseInt(cf.centerPoint.y, 10) - th.radius;
            cf.left = parseInt(cf.centerPoint.x, 10) - th.radius;

            // 添加样式
            var cssStr = th.setStyle();
            $('head').append(cssStr);
            
            // 初始化DOM
            var htm = '<div class="radar-container">';
            for(var i = 0; i < cf.waveNum; i++){
                htm += '    <div class="radar-wave" style="animation-delay: ' + i + 's;"></div>';
            }
            htm += ''
            + '    <div class="radar-sector"><div class="radar-scan-top"></div></div>'
            + '    <div class="radar-center"></div>'
            + '</div>';
            th.dom = $(htm).appendTo(th.parent);
            th.centerPointDom = $('.radar-center');
            th.scanTopPointDom = $('.radar-scan-top');
        }
    }
    radar.init(cfg);
    return radar;
};

$.fn.addRadar = function (cfg){
    return new Radar(this, cfg);
}

接着就是来个网页,调用该radar:

<!DOCTYPE html>
<html style="font-size: 20px;">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>雷达扫描效果</title>
    <link href="../img/favicon.ico" rel="icon" type="image/x-icon">
    <link href="../css/site.css" rel="stylesheet" type="text/css">
    <script type="text/javascript" src="../js/modernizr-2.6.2.js"></script>
    <script type="text/javascript" src="../js/jquery-3.1.1.min.js"></script>
    <script type="text/javascript" src="../js/radar.js"></script>
    <style type="text/css">
        .addFlyBtn{
            position: absolute; width: 160px;height: 40px;line-height: 40px;top: 60px; left:1500px; font-size: 16px; font-weight: bold; border-radius: 5px; text-align: center;  background-color: rgba(30, 60, 140, 0.6); cursor: pointer;
        }
    </style>
</head>

<body style="font-size: 20px;">
    <div class="body" style="background: url(../img/map.png) no-repeat 0 0; background-size: cover;"></div>
    <div class="addFlyBtn">随机一个飞行点</div>
</body>
    <script type="text/javascript">
        var radar = $('.body').addRadar({
            waveNum: 8,
            frequency: 1,
            radius: '400px',
            centerPoint: { x: '450px', y: '450px'}
        });

        $('.addFlyBtn').on('click', function(){
            radar.addFlyPoint();
            if(!radar.scanInterval){
                radar.scan();
            }
        });
      
    </script>
</html>

本插件还有部分参数没有完成替代,有兴趣的同学可以自行补完。整个雷达效果其实代码很少,倒是为了加入飞行点扫描效果,增加了大量代码,有能力的同学,可以尝试帮忙简化一下这块的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值