css繪制聲吶超聲波圖的詳細步驟
1、需求簡述;
2、CSS繪制90度扇形;
3、添加分隔線繪制成一個超聲波圖樣式的聲吶圖;
4、添加聲吶圖深水樣式的背景;
5、根據(jù)聲吶探測器返回數(shù)據(jù)計算魚的位置;
6、根據(jù)聲吶探測器返回數(shù)據(jù)繪制水深標尺;
7、完整代碼;
8、知識點學習。
1.需求簡述:
最近做一個項目有這么一個需求,根據(jù)聲吶探測器返回水下魚的位置數(shù)據(jù),把魚顯示到扇形內(nèi),聲吶數(shù)據(jù)包含總水深、n條魚的水深二維數(shù)組(如:[[3452,8569,…],[...],[...],[...],[...]],單位毫米),隨機顯示到扇形相應(yīng)的深度處即可。為什么是二維數(shù)組呢?因為聲吶探測器有5個探頭(如圖1所示),返回的數(shù)據(jù)是包含了5個探頭的回波數(shù)據(jù),回波數(shù)據(jù)數(shù)組里的一個數(shù)字就代表一條魚的深度位置,現(xiàn)要把1、2、3探頭的數(shù)據(jù)展示到聲吶圖中間那格,4、5號探頭數(shù)據(jù)分別顯示到聲吶圖左右兩格。
圖1:聲吶探頭示意圖
下面是最終效果圖
最終效果圖
看到這里的前端同學可以先暫停想想怎么實現(xiàn)哦,看完的同學們可以在評論區(qū)自己的方法哦,我也想借鑒一下,我覺得可能用canvas可能會更好。
這里用的是 Vue2+Less。
CSS繪制一個90度扇形
這是第一個難點,這里我嘗試過svg的方式,但是沒搞定,最后還是回到css來吧。
首先css是沒辦法直接畫一個扇形的,因為我們的html元素都是四邊形的,就算加了圓角,也只能顯示個圓形。這里就要用的css高階一點點的寫法了:css clip-path屬性。clip-path 用法將在文章底部總結(jié)知識點介紹。
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;
}
添加以上代碼后,得到效果:
超聲波效果
添加分隔線繪制成一個超聲波圖樣式的聲吶圖
添加兩條傾斜的虛線作為分隔線,把扇形分成三格,虛線的傾斜和定位很關(guān)鍵。
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)翻轉(zhuǎn)了圓形,使扇形朝下,所以現(xiàn)在我們需要用一個水面朝下的圖片作為背景圖素材。
翻轉(zhuǎn)后的背景圖素材
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標簽添加個好看協(xié)調(diào)點的背景,得到以下效果:
添加背景后的效果
根據(jù)聲吶探測器返回數(shù)據(jù)計算魚的位置
在vue文件的data模擬探測器回波數(shù)據(jù):
data() {
// 回波數(shù)據(jù)
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 // 統(tǒng)計數(shù)
};
},
下面是計算魚的位置的關(guān)鍵代碼,利用了三角函數(shù)的正弦函數(shù)sinθ、余弦函數(shù)cosθ,對應(yīng)的js方法:Math.sin(ag * Math.PI / 180)、Math.cos(ag * Math.PI / 180),在vue文件的methods新增方法:
/*
三角函數(shù),獲取魚在屏幕上的顯示位置
h魚的水深度,
grid=1,2,3 魚屬于扇形的分區(qū)索引,
第一格角度 = 15~45,第二格= 0~15,第二格區(qū)分左右部分,第三格= 15~45,
*/
getXY(h, hh, grid = 2) {
let ag = 0; // 角度
let lr = 'L'; // 第二格隨機區(qū)分左右, L/R
// 1.根據(jù)魚的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.獲取扇形相關(guān)數(shù)據(jù)
let sona = this.$refs['sona'];
let L = sona.offsetWidth / 2;
let r = $('.round1').width() / 2;
// 3.計算
let p = hh / r;
let LL = p * L; // 屏幕像素轉(zhuǎn)換為毫米長度
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.隨機取魚圖標的旋轉(zhuǎn)角度
let st = -60; let end = 240;
let deg = Math.floor(Math.random() * (st - end) + end);
xy.transform = 'rotateZ(' + deg + 'deg)';
this.XYArr.push(xy);
},
遍歷二維數(shù)組生成魚的位置
// 生成魚的位置
createFish() {
this.XYArr = []; // 清空魚的數(shù)據(jù)
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>
得到以下魚的分布效果:
魚的分布效果
根據(jù)聲吶探測器返回數(shù)據(jù)繪制水深標尺
最后根據(jù)探測器返回的總水深,生成參考標尺。
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>
知識點總結(jié)
1、簡單了解CSS中的路徑裁剪 clip-path
clip-path 屬性使用裁剪方式創(chuàng)建元素的可顯示區(qū)域。區(qū)域內(nèi)的部分顯示,區(qū)域外的隱藏。可以指定一些特定形狀。
語法: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 使用內(nèi)邊距框作為引用框
content-box 使用內(nèi)容框作為引用框
fill-box 使用對象邊界框作為引用框
stroke-box 使用筆觸邊界框(stroke bounding box)作為引用框
view-box 使用最近的 SVG 視口(viewport)作為引用框。
none 這是默認設(shè)置,不剪輯
initial 設(shè)置屬性為默認值。
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三角函數(shù)
Math.sin()
sin 方法返回一個 -1 到 1 之間的數(shù)值,表示給定角度(單位:弧度)的正弦值。
Math.sin(x) //函數(shù)返回一個數(shù)值的正弦值。x為弧度
Math.sin(0); // 0
Math.sin(Math.PI / 2); // 1
Math.cos()
cos 方法返回一個 -1 到 1 之間的數(shù)值,表示角度(單位:弧度)的余弦值。
Math.cos(0); // 1
Math.cos(Math.PI); // -1
Math.cos(2 * Math.PI); // 1
Math.tan()
表示一個角的正切值。
Math.atan()
函數(shù)返回一個數(shù)值的反正切(以弧度為單位)
Math.atan(0); // 0
已知兩直角邊Y,X長度,求夾角角度:
180*Math.atan(Y/X)/(Math.PI)
*創(chuàng)作不易,轉(zhuǎn)載請注明出處并附上本文鏈接