CSS 绘制扇面

参考资料:https://juejin.cn/post/7266641059282927650

 

效果:

源码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>使用css3绘制任意角度扇形</title>
  <style>
    .sector {
        position: relative;
        display: inline-block;
        width: 80px;
        height: 80px;
        background-color: transparent;
        border-radius: 50%;
        overflow: hidden;
        transform: rotate(45deg);
    }

    .sector::before {
        display: inline-block;
        content: '';
        width: 100%;
        height: 100%;
        background: linear-gradient(90deg,
                                    #4D84FF 25%,#eee 0,
                                    #eee 50%,#4D84FF 0,
                                    #4D84FF 75%,#eee 0);
        background-size: 30px;
        clip-path: polygon(50% 50%, 0 0, 100% 0);
        opacity: .7;
    }
    
    .sector::after {
        position: absolute;
        display: inline-block;
        content: '';
        width: 80px;
        height: 80px;
        top: calc(50% - 40px);
        left: calc(50% - 40px);
        border-radius: 50%;
        background-color: #fff;
        z-index: 1;
    }

  </style>
</head>
<body>
  <div class="sector"></div>
</body>

<script>
    /**
     * @param {Number} radius: 外半径
     * @param {Number} minRadius: 内半径
     * @param {Number} angles: 角度
     * @param {Number} direct: 方向
     **/
    function drawPie({radius, minRadius, angles, direct, selector}) {
        let points = ['50% 50%', '0 0'],
            residue = angles%45? angles%45:45,
            percent = 0
            direct = direct || 45;
        
        angles > 90 && points.push('100% 0');
        angles > 180 && points.push('100% 100%');
        angles > 270 && points.push('0 100%');
        
        //let percent = (100/2) * (Math.tan(2*Math.PI/360 * residue).toFixed(4)); // tan算出来的是相对半径的占比
        
        percent = (100/2) * (Math.tan(2*Math.PI/360 * residue).toFixed(4)); 
        
        if(angles<=45) {
            points.push(percent + '%' + ' 0');
        }else if(angles<=90) {
            points.push(percent + 50 + '%' + ' 0');
        }else if(angles<=135) {
            points.push('100% ' + percent + '%');
        }else if(angles<=180) {
            points.push('100% ' + (percent + 50) + '%');
        }else if(angles<=225) {
            points.push(100 - percent + '%' + ' 100%');
        }else if(angles<=270) {
            points.push(50 - percent + '%' + ' 100%');
        }else if(angles<=315) {
            points.push('0 ' + (100 - percent) + '%');
        }else if(angles<=360) {
            points.push('0 ' + (50 - percent) + '%');
        }
        
        let path = 'polygon(' + points.join(', ') + ')';
        // 外扇面
        addCSSRule(selector + '::before', {
            'clip-path': path
        });
        // 内扇面
        addCSSRule(selector + '::after', {
            'clip-path': path,
            width: minRadius + 'px',
            height: minRadius + 'px',
            top: 'calc(50% - ' + minRadius/2 + 'px)',
            left: 'calc(50% - ' + minRadius/2 + 'px)'
        });
        // 容器大小和方向
        addCSSRule(selector, {
            width: radius + 'px',
            height: radius + 'px',
            transform: 'rotate(' + direct + 'deg)'
        });
    }
    // 绘制扇面
    drawPie({
        selector: '.sector',
        radius: 400,     // 外半径
        minRadius: 180,  // 内半径
        angles: 120,     // 弧度
        direct: 100      // 方向
    });
    
    function addCSSRule(selector, rules, index) {
        // 创建一个style元素
        var style = document.createElement('style');
        
        // 设置type属性为text/css
        style.type = 'text/css';
        
        // 插入到head中
        document.head.appendChild(style);
        
        // 获取sheet
        var sheet = style.sheet;
        
        // 如果index未提供,则添加到末尾
        index = index || null;
        
        // 如果是CSS规则字符串
        if (typeof rules === 'string') {
            // 直接添加
            sheet.insertRule(selector + ' {' + rules + '}', index);
        } else { // 如果是一个对象
            // 遍历对象中的所有属性
            for (var prop in rules) {
                if (rules.hasOwnProperty(prop)) {
                    // 将属性和值转换为字符串
                    var rule = prop + ': ' + rules[prop];
                    // 添加到样式表中
                    sheet.insertRule(selector + ' {' + rule + '}', index);
                }
            }
        }
    }
    
    /*
    addCSSRule('.my-other-class', 'color: red; background-color: yellow;', 0); // 添加到顶部
    */
</script>

</html>

 进阶联动版

 代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>使用css3绘制任意角度扇形</title>
  <style>
    html,body {
        height: 98%;
    }
    body {
        background: url(https://t11.baidu.com/it/u=2644047138,236799825&fm=30&app=106&f=JPEG?w=640&h=439&s=C250CF3093F3C1CE5670B8DE0300D0B1) center center fixed no-repeat;
    }
    .sector-ctn {
        position: absolute;

        top: 45%;
        left: 40%;
    }
    .sector-ctn.expand {
        display: block;
    }
    .cell-sector-ctn {
        position: absolute;

        top: 50%;
        left: 50%;
    }
    .cell-sector-ctn::before {
        top: -5.5px;
        left: -5.5px;
        display: inline-block;
        position: absolute;
        content: '';
        width: 11px;
        height: 11px;
        border-radius: 11px;
        background: linear-gradient(180deg, #4F96FE 0%, #2779F5 100%);
        z-index: 100;
    }
    /* 扇面 */
    .sector, .cell-sector {
        position: absolute;
        display: inline-block;
        background-color: transparent;
        border-radius: 50%;
        overflow: hidden;
    }

    .sector::before, .cell-sector::before {
        display: inline-block;
        content: '';
        width: 100%;
        height: 100%;
        background-image: radial-gradient(
                            transparent 0%,
                            rgb(77 132 255 / 10%) 10px,
                            rgb(77 132 255 / 40%) 20px,
                            rgb(77 132 255 / 100%) 36px,
                            rgb(77 132 255 / 95%) 15%,
                            rgb(77 132 255 / 90%) 20%,
                            rgb(77 132 255 / 85%) 30%,
                            rgb(77 132 255 / 75%) 40%,
                            rgb(77 132 255 / 60%) 50%,
                            rgb(77 132 255 / 56%) 52%,
                            rgb(115 195 255 / 80%) 61%,
                            rgb(249 249 249 / 50%) 80%,
                            rgb(249 249 249 / 0.1%) 95%,
                            transparent 100%
                        );
        
        opacity: .8;
    }

    .sector::after, .cell-sector::after {
        position: absolute;
        display: inline-block;
        content: '';
        border-radius: 50%;
        background-color: rgb(255 255 255 / 65%);
        z-index: 1;
    }

    .sector {
        display: none;
    }
    .sector.expand {
        display: inline-block;
    }
    
    .cell-sector::before {
        background-image: radial-gradient(
                            #fff 0%,
                            #fff 98%,
                            rgb(77 132 255 / 100%) 100%
                        );
        opacity: 1;
    }
    .cell-sector::after {
        background-color: rgb(0 0 0 / 15%);
    }
    .cell-sector.select::before {
        background-image: radial-gradient(
                            rgb(77 132 255 / 50%) 0%,
                            rgb(77 132 255 / 80%) 40%,
                            rgb(77 132 255 / 100%) 100%
                        );
        opacity: .8;
    }
    .cell-sector.expand::before,
    .cell-sector.expand::after {
        background-image: none;
        background-color: transparent;
    }

    .hover-square {
        position: absolute;
        top: -12.5px !important;
        left:  -12.5px !important;
        width: 25px !important;
        height: 25px !important;
        background-color:aqua;
        z-index: 1000;
        opacity: .1;
        border-radius: 0 0 25px 0;
    }
  </style>
</head>
<body>
    <div class="sector-ctn">
        <div class="sector"></div>
        <div class="cell-sector-ctn">
          <div class="cell-sector cell-1"></div>
          <div class="cell-sector cell-2"></div>
          <div class="cell-sector cell-3"></div>

          <div onclick="cellClick(this, event)" cell="1" class="hover-square cell-1"></div>
          <div onclick="cellClick(this, event)" cell="2" class="hover-square cell-2"></div>
          <div onclick="cellClick(this, event)" cell="3" class="hover-square cell-3"></div>
        </div>
    </div>
</body>

<script>
    let direct = 1;
    
    let cellOptions = {
            radius: 36,
            minRadius: 15,
            angles: 115,
            direct: direct,
            translate: 'translate(12.5px, 12.5px)'
        };
    
    let base = {
            selector: '.sector',
            radius: 160,     // 外半径
            minRadius: 60,   // 内半径
            angles: 115,  // 弧度
            direct: direct,  // 方向
        };

    document.body.removeEventListener('click', blurCell);
    document.body.addEventListener('click', blurCell);
    
    function blurCell(evt) {
        console.log('outer click')
        document.querySelector('.sector').classList.remove('expand');
        document.querySelectorAll('.cell-sector').forEach((item)=>{
            item.classList.remove('expand');
        });
    }

    function cellClick(dom, evt) {
        let target = evt.target,
            cellIndex = target.getAttribute('cell');
        console.log(target, cellIndex)
        // 动态绘制小区信号扇面
        drawPie({
            selector: base.selector,
            radius: base.radius,     // 外半径
            minRadius: base.minRadius,   // 内半径
            angles: base.angles,  // 弧度
            direct: base.direct + (cellIndex - 1)*120   // 方向
        });
        // 添加expand类,展示信号区域
        document.querySelectorAll('.cell-sector').forEach((item)=>{
            item.classList.remove('expand');
            item.classList.remove('select');
        });
        let curCell = document.querySelector('.cell-sector.cell-'+cellIndex);
        curCell.classList.add('expand');
        curCell.classList.add('select');
        document.querySelector('.sector').classList.add('expand');

        evt.stopPropagation();
    }

    /**
     * @param {Number} radius: 外半径
     * @param {Number} minRadius: 内半径
     * @param {Number} angles: 角度
     * @param {Number} direct: 方向
     **/
    function drawPie({radius, minRadius, angles, direct, selector}) {
        let points = ['50% 50%', '0 0'],
            residue = angles%45? angles%45:45,
            percent = 0
            direct = direct || 45;
        
        angles > 90 && points.push('100% 0');
        angles > 180 && points.push('100% 100%');
        angles > 270 && points.push('0 100%');
        
        //let percent = (100/2) * (Math.tan(2*Math.PI/360 * residue).toFixed(4)); // tan算出来的是相对半径的占比
        
        percent = (100/2) * (Math.tan(2*Math.PI/360 * residue).toFixed(4)); 
        
        if(angles<=45) {
            points.push(percent + '%' + ' 0');
        }else if(angles<=90) {
            points.push(percent + 50 + '%' + ' 0');
        }else if(angles<=135) {
            points.push('100% ' + percent + '%');
        }else if(angles<=180) {
            points.push('100% ' + (percent + 50) + '%');
        }else if(angles<=225) {
            points.push(100 - percent + '%' + ' 100%');
        }else if(angles<=270) {
            points.push(50 - percent + '%' + ' 100%');
        }else if(angles<=315) {
            points.push('0 ' + (100 - percent) + '%');
        }else if(angles<=360) {
            points.push('0 ' + (50 - percent) + '%');
        }
        
        let path = 'polygon(' + points.join(', ') + ')',
            zoom = 5;
        try{
            zoom = globMap.getZoom();
        }catch(e){}

        let rateRels = {
                0: 0.10,
                1: 0.10,
                2: 0.10,
                3: 0.10,
                4: 0.13,
                5: 0.25,
                6: 0.45,
                7: 0.75,
                8: 1.3,
                9: 2,
                10: 2,
                11: 3,
                12: 3,
                13: 5,
                14: 5,
                15: 8
            },
            rate = rateRels[zoom]*zoom;
        console.log('zoom: ', zoom, rate)

        // 外扇面
        addCSSRule(selector + '::before', {
            'clip-path': path
        });
        // 内扇面
        addCSSRule(selector + '::after', {
            'clip-path': path,
            width: minRadius*rate + 'px',
            height: minRadius*rate + 'px',
            top: 'calc(50% - ' + minRadius*rate/2 + 'px)',
            left: 'calc(50% - ' + minRadius*rate/2 + 'px)'
        });
        // 容器大小和方向
        addCSSRule(selector, {
            top: '-' + radius*rate/2 +'px',
            left: '-' + radius*rate/2 +'px',
            width: radius*rate + 'px',
            height: radius*rate + 'px',
            transform: 'rotate(' + direct + 'deg)'
        });
    }
    
    // 绘制扇面
    /*
    drawPie({
        selector: '.sector',
        radius: 160,     // 外半径
        minRadius: 60,   // 内半径
        angles: angles,  // 弧度
        direct: direct   // 方向
    });
    */

    // cell-1
    drawPie({
        selector: '.cell-sector-ctn .cell-1',
        radius: cellOptions.radius,        // 外半径
        minRadius: cellOptions.minRadius,  // 内半径
        angles: cellOptions.angles,        // 弧度
        direct: cellOptions.direct,        // 方向
        translate: cellOptions.translate
    });
    // cell-2
    drawPie({
        selector: '.cell-sector-ctn .cell-2',
        radius: cellOptions.radius,        // 外半径
        minRadius: cellOptions.minRadius,  // 内半径
        angles: cellOptions.angles,        // 弧度
        direct: cellOptions.direct + 120,  // 方向
        translate: cellOptions.translate
    });
    // cell-3
    drawPie({
        selector: '.cell-sector-ctn .cell-3',
        radius: cellOptions.radius,        // 外半径
        minRadius: cellOptions.minRadius,  // 内半径
        angles: cellOptions.angles,        // 弧度
        direct: cellOptions.direct + 240,  // 方向
        translate: cellOptions.translate
    });
    
    let squareDirect = direct - 127,
        squareTranslate = cellOptions.translate;
    // 容器大小和方向
    addCSSRule('.hover-square.cell-1', {
        transform: 'rotate(' + squareDirect + 'deg)' + ' ' + squareTranslate + ' !important'
    });
    // 容器大小和方向
    addCSSRule('.hover-square.cell-2', {
        transform: 'rotate(' + (squareDirect + 120) + 'deg)' + ' ' + squareTranslate + ' !important'
    });
    // 容器大小和方向
    addCSSRule('.hover-square.cell-3', {
        transform: 'rotate(' + (squareDirect + 240) + 'deg)' + ' ' + squareTranslate + ' !important'
    });
    
    function addCSSRule(selector, rules, index) {
        // 创建一个style元素
        var style = document.createElement('style');
        
        // 设置type属性为text/css
        style.type = 'text/css';
        
        // 插入到head中
        document.head.appendChild(style);
        
        // 获取sheet
        var sheet = style.sheet;
        
        // 如果index未提供,则添加到末尾
        index = index || null;
        
        // 如果是CSS规则字符串
        if (typeof rules === 'string') {
            // 直接添加
            sheet.insertRule(selector + ' {' + rules + '}', index);
        } else { // 如果是一个对象
            // 遍历对象中的所有属性
            for (var prop in rules) {
                if (rules.hasOwnProperty(prop)) {
                    // 将属性和值转换为字符串
                    var rule = prop + ': ' + rules[prop];
                    // 添加到样式表中
                    sheet.insertRule(selector + ' {' + rule + '}', index);
                }
            }
        }
    }
    
    /*
    addCSSRule('.my-other-class', 'color: red; background-color: yellow;', 0); // 添加到顶部
    */
</script>

</html>

 

posted @ 2024-08-12 13:48  【云】风过无痕  阅读(36)  评论(0编辑  收藏  举报