<返回更多

CSS绘制声呐图的详细步骤,三角函数和CSS技术高阶应用

2023-03-30  今日头条  lyf风去云往
加入收藏

css绘制声呐超声波图的详细步骤

1、需求简述;

2、CSS绘制90度扇形;

3、添加分隔线绘制成一个超声波图样式的声呐图;

4、添加声呐图深水样式的背景;

5、根据声呐探测器返回数据计算鱼的位置;

6、根据声呐探测器返回数据绘制水深标尺;

7、完整代码;

8、知识点学习。

1.需求简述:

最近做一个项目有这么一个需求,根据声呐探测器返回水下鱼的位置数据,把鱼显示到扇形内,声呐数据包含总水深、n条鱼的水深二维数组(如:[[3452,8569,…],[...],[...],[...],[...]],单位毫米),随机显示到扇形相应的深度处即可。为什么是二维数组呢?因为声呐探测器有5个探头(如图1所示),返回的数据是包含了5个探头的回波数据,回波数据数组里的一个数字就代表一条鱼的深度位置,现要把1、2、3探头的数据展示到声呐图中间那格,4、5号探头数据分别显示到声呐图左右两格。

图1:声呐探头示意图

下面是最终效果图

最终效果图

看到这里的前端同学可以先暂停想想怎么实现哦,看完的同学们可以在评论区自己的方法哦,我也想借鉴一下,我觉得可能用canvas可能会更好。

这里用的是 Vue2+Less。

CSS绘制一个90度扇形

这是第一个难点,这里我尝试过svg的方式,但是没搞定,最后还是回到css来吧。

首先css是没办法直接画一个扇形的,因为我们的html元素都是四边形的,就算加了圆角,也只能显示个圆形。这里就要用的css高阶一点点的写法了:css clip-path属性。clip-path 用法将在文章底部总结知识点介绍。

html

<section ref="sona" class="maxbox fxbox" style="background: #ccc;">
    <div class="sx_cp">
        <div class="round1 path"></div>
    </div>
</section>

section 标签为一个正方形,用于为扇形可以绝对定位,div class="sx_cp" 为扇形容器,div class="round1 path" 为扇形主体。

css

.fxbox {
    display: flex;
    align-items: center;
    justify-content: center;
}
.sx_cp {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 6%;
}
.round1 {
    width: 140%;
    height: 140%;
    position: absolute;
    top: -70%; // -140/2
    left: -20%; // (100-140)/2
}
.path {
    clip-path: polygon(0% 0%, 100% 0%, 50% 50%, 100% 100%);
    border-radius: 50%;
    background-color: rgba(0, 225, 239, .1);
    box-shadow: inset 0 0 40px 1px rgba(1, 240, 255, 0.85);
    border: rgba(1, 240, 255, 1) solid 1px;
    transform: rotateZ(180deg);
}

这段代码得到一个扇形雏形效果:

扇形雏形效果

有了这个雏形,接下来就明了了,再加3个逐渐缩小的扇形就可以了。

html

<div class="sx_cp">
    <div class="round1 path"></div>
    <div class="round2 path"></div>
    <div class="round3 path"></div>
    <div class="round4 path"></div>
</div>

css

.round2 {
    width: 100%;
    height: 100%;
    position: absolute;
    top: -50%; // -100/2
		left: 0%; // (100-100)/2
		z-index: 2;
}
.round3 {
    width: 60%;
    height: 60%;
    position: absolute;
    top: -30%;
    left: 20%; // (100-60)/2
		z-index: 3;
}
.round4 {
    width: 20%;
    height: 20%;
    position: absolute;
    top: -10%;
    left: 40%; // (100-80)/2
		z-index: 3;
}

添加以上代码后,得到效果:

超声波效果

添加分隔线绘制成一个超声波图样式的声呐图

添加两条倾斜的虚线作为分隔线,把扇形分成三格,虚线的倾斜和定位很关键。

html

......

<div class="round4 path"></div>

<div class="line1"></div>

<div class="line2"></div>

css

.line1 {
    border-left: rgba(0, 225, 239, .6) dashed 1px;
    width: 0;
    height: 70%;
    position: absolute;
    transform: rotateZ(165deg);
    top: -1%;
    left: 59%;
}
.line2 {
    border-left: rgba(0, 225, 239, .7) dashed 1px;
    width: 0;
    height: 70%;
    position: absolute;
    transform: rotateZ(195deg);
    top: -1%;
    left: 41%;
}

添加后得到以下效果:

效果

添加声呐图深水样式的背景

从上面图可以看出,扇形最大的半径就是最外面那个圆的半径,那就直接给最外面的圆添加一个背景图,就是整个扇形超声波图的背景了。

开始裁剪圆形得到扇形时,使用transform: rotateZ(180deg)翻转了圆形,使扇形朝下,所以现在我们需要用一个水面朝下的图片作为背景图素材。

翻转后的背景图素材

css

.round1 {
    background-image: url(./bg.jpg);
    background-position: center center;
    background-size: 100% 100%;
    width: 140%;
    height: 140%;
    position: absolute;
    top: -70%; // -140/2
    left: -20%; // (100-140)/2
}

再给section标签添加个好看协调点的背景,得到以下效果:

添加背景后的效果

根据声呐探测器返回数据计算鱼的位置

在vue文件的data模拟探测器回波数据:

data() {
   // 回波数据
    this.snData = {
        depth: 4568,
        temperature: 18,
        echo_data: [[3299, 3299, 3200, 3399, 3299, 4400, 1230], [3333, 2222],
                    			[1234, 3210], [1234], [3210, 1234, 5000, 5000]]
    };
    return {
        XYArr: [], // {left, top, transform: rotateZ(0deg)}
        scale: [], // 参考线刻度
        hh: 0, // 水的总深度
        temperature: 0, // 水温
        count: 0 // 统计数
    };
},

 

下面是计算鱼的位置的关键代码,利用了三角函数的正弦函数sinθ、余弦函数cosθ,对应的js方法:Math.sin(ag * Math.PI / 180)、Math.cos(ag * Math.PI / 180),在vue文件的methods新增方法:

/*
三角函数,获取鱼在屏幕上的显示位置
h鱼的水深度,
grid=1,2,3 鱼属于扇形的分区索引,
第一格角度 = 15~45,第二格= 0~15,第二格区分左右部分,第三格= 15~45,
 */
getXY(h, hh, grid = 2) {
    let ag = 0; // 角度
    let lr = 'L'; // 第二格随机区分左右, L/R

    // 1.根据鱼的grid随机取扇形角度
    if (grid === 1) {
        ag = Math.floor(Math.random() * 15 + 25);
    }
    if (grid === 2) {
        ag = Math.round(Math.random() * 15);
        lr = ag % 2 === 0 ? 'R' : 'L';
    }
    if (grid === 3) {
        ag = Math.floor(Math.random() * 15 + 25);
        lr = 'R';
    }

    // 2.获取扇形相关数据
    let sona = this.$refs['sona'];
    let L = sona.offsetWidth / 2;
    let r = $('.round1').width() / 2;

    // 3.计算
    let p = hh / r;
    let LL = p * L; // 屏幕像素转换为毫米长度

    let xy = {};

    let x = LL - h * Math.sin(ag * Math.PI / 180);
    let psx = x / (LL * 2);
    if (lr === 'L') {
        xy.left = psx * 100 + '%';
    } else {
        xy.left = (LL * 2 - x) / (LL * 2) * 100 + '%';
    }

    let y = h * Math.cos(ag * Math.PI / 180);
    xy.top = y / (LL * 2) * 100 + '%';

    // 4.随机取鱼图标的旋转角度
    let st = -60; let end = 240;
    let deg = Math.floor(Math.random() * (st - end) + end);
    xy.transform = 'rotateZ(' + deg + 'deg)';

    this.XYArr.push(xy);
},

遍历二维数组生成鱼的位置

// 生成鱼的位置
createFish() {
    this.XYArr = []; // 清空鱼的数据
    this.getScale(this.hh, this.snData.depth);
    this.snData.echo_data.forEach((item, idx) => {
        // idx = 0,1,3 为扇形中间格;2为左边格,4为右边格
        let gird = 2;
        if (idx === 2) {
            gird = 1;
        } else if (idx === 4) {
            gird = 3;
        }
        item.forEach(fitem => {
            if (fitem > this.hh) fitem = this.hh;
            if (fitem < 600) fitem = 600;
            this.getXY(fitem, this.hh, gird);
        });
    });
},

 

鱼的图标样式:

css

.fsIcon {
    position: absolute;
    z-index: 5;
    left: 0;
    top: 0;
    width: 26px;
    height: 10px;
    background: url(./yu.png) no-repeat center center;
    background-size: 100% 100%;
    margin-top: -2px;
    margin-left: -13px;
    transform: rotateZ(40deg); // -60~240
    opacity: .9;
}

 

html,for生成鱼图标

    .......
    <div class="line1"></div>
    <div class="line2"></div>
     <i v-for="(item, idx) in XYArr" :key="idx" class="fsIcon"  :style="item"></i> 

得到以下鱼的分布效果:

鱼的分布效果

根据声呐探测器返回数据绘制水深标尺

最后根据探测器返回的总水深,生成参考标尺。

html

<div class="ruler">
  <span v-for="item in scale" :key="item">{{ item }}米</span>
</div>

css

.ruler {
       width: 0;
       height: 70%;
       position: absolute;
       transform: rotateZ(45deg);
       top: -10%;
       left: 25%;

   span {
       white-space: nowrap;
       position: absolute;
       right: -100%;
       z-index: 3;
       font-size: 12px;
       color: #eee;

       &:nth-child(1) {
            top: calc(100% - 20px); // 140
        }
       &:nth-child(2) {
            top: 70%;
        }
       &:nth-child(3) {
            top: 40%;
        }
       &:nth-child(4) {
            top: 10%;
        }
   }
}

JAVAScript

// 扇形参考线刻度
getScale(old_hh, new_hh) {
    if (Math.abs(new_hh - old_hh) < 200) return; // 两次深度小于0.x米不作处理
    this.hh = new_hh;
    this.scale = [];
    for (let i = 0; i <= 3; i++) {
        let item = ((new_hh - new_hh / 4 * i) / 1000).toFixed(1);
        this.scale.push(item);
    }
}

最终效果展示

最终效果展示

完整代码

上文的css和js代码是完整的,下面是完整的html:

<div ref="sona" class="maxbox fxbox">
  <div class="sx_cp">
    <div class="round1 path"></div>
    <div class="round2 path"></div>
    <div class="round3 path"></div>
    <div class="round4 path"></div>
    <div class="line1"></div>
    <div class="line2"></div>
    <div class="ruler">
      <span v-for="item in scale" :key="item">{{ item }}米</span>
    </div>
    <i v-for="(item, idx) in XYArr" :key="idx" class="fsIcon"  :style="item"></i>
  </div>
</div>

知识点总结

1、简单了解CSS中的路径裁剪 clip-path

clip-path 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。可以指定一些特定形状。

语法:clip-source|basic-shape|margin-box|border-box|padding-box|content-box|fill-box|stroke-box|view-box|none|initial|inherit;

属性值 描述

clip-source 用 URL 表示剪切元素的路径

basic-shape 将元素裁剪为基本形状:圆形、椭圆形、多边形或插图

margin-box 使用外边距框作为引用框

border-box 使用边框作为引用框

padding-box 使用内边距框作为引用框

content-box 使用内容框作为引用框

fill-box 使用对象边界框作为引用框

stroke-box 使用笔触边界框(stroke bounding box)作为引用框

view-box 使用最近的 SVG 视口(viewport)作为引用框。

none 这是默认设置,不剪辑

initial 设置属性为默认值。

inherit 属性值从父元素继承。

其中basic-shape属性的值有几种:

polygon多边形、circle圆形、ellipse椭圆、inset插图

三角形

clip-path: polygon(50% 0%, 0% 100%, 100% 100%);

菱形

clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);

梯形

clip-path: polygon(20% 0%, 80% 0%, 100% 100%, 0% 100%);

平行四边形

clip-path: polygon(25% 0%, 100% 0%, 75% 100%, 0% 100%);

斜角

clip-path: polygon(20% 0%, 80% 0%, 100% 20%, 100% 80%, 80% 100%, 20% 100%, 0% 80%, 0% 20%);

左箭头

clip-path: polygon(40% 0%, 40% 20%, 100% 20%, 100% 80%, 40% 80%, 40% 100%, 0% 50%);

右箭头

clip-path: polygon(0% 20%, 60% 20%, 60% 0%, 100% 50%, 60% 100%, 60% 80%, 0% 80%);

五角星

clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);

 

2、简单了解js三角函数

Math.sin()
sin 方法返回一个 -1 到 1 之间的数值,表示给定角度(单位:弧度)的正弦值。

Math.sin(x)   //函数返回一个数值的正弦值。x为弧度
Math.sin(0);           // 0
Math.sin(Math.PI / 2); // 1

Math.cos()
cos 方法返回一个 -1 到 1 之间的数值,表示角度(单位:弧度)的余弦值。

Math.cos(0);           // 1
Math.cos(Math.PI);     // -1
Math.cos(2 * Math.PI); // 1

Math.tan()
表示一个角的正切值。

Math.atan()
函数返回一个数值的反正切(以弧度为单位)

Math.atan(0);  // 0

已知两直角边Y,X长度,求夹角角度:

180*Math.atan(Y/X)/(Math.PI)

 

*创作不易,转载请注明出处并附上本文链接

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>