日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
圖形編輯器:標(biāo)尺功能的實(shí)現(xiàn)

大家好,我是前端西瓜哥。今天我們來實(shí)現(xiàn)圖形編輯器的標(biāo)尺功能。

成都創(chuàng)新互聯(lián)主營杏花嶺網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶App定制開發(fā),杏花嶺h5微信小程序開發(fā)搭建,杏花嶺網(wǎng)站營銷推廣歡迎杏花嶺等地區(qū)企業(yè)咨詢

項(xiàng)目地址:

https://github.com/F-star/suika

線上體驗(yàn):

https://blog.fstars.wang/app/suika/

標(biāo)尺指的是畫布上邊和左邊的兩個有刻度的尺子,作用讓用戶知道他正在編輯的視口所在位置范圍。

我們的需求是:間隔特定的長度,繪制一個刻度,并顯示這個刻度在 X 軸或 Y 軸上的位置。

先看最終實(shí)現(xiàn)效果:

標(biāo)尺功能演示

可以看到,視口移動后,標(biāo)尺上的刻度能正確地改變。此外縮放畫布,標(biāo)尺的步長會發(fā)生改變,保持一個比較適合的密度。

實(shí)現(xiàn)思路

總體實(shí)現(xiàn)思路:

  1. 確定刻度尺的步長(step)。步長是和畫布縮放比(zoom)相關(guān)的,zoom 越大,step 就越小;
  2. 計(jì)算出需要繪制的所有刻度。分別為從視口從左側(cè)到右側(cè),從上邊到下邊的范圍;
  3. 繪制。繪制上也是有考量的,先繪制背景,然后繪制刻度,最后繪制分界線。

步長選擇

步長會根據(jù) zoom 進(jìn)行設(shè)置,目的是讓視口中的標(biāo)尺能繪制適宜密度的刻度。

假設(shè)我們的步長固定為 50,不跟隨 zoom 改變,在 100% 看起來效果不錯:

但當(dāng)你縮小時(shí),會變成下面這樣:

密度過大,導(dǎo)致數(shù)字重疊。同樣,放大時(shí)則過于稀疏,刻度很難才見到一個,沒能發(fā)揮標(biāo)尺的效用。

步長怎么計(jì)算呢?

理論上步長可以是 50,那么 51 好像也行,3 也行。但更建議使用 5 的倍數(shù)、2 的倍數(shù)、25 的倍數(shù)這些作為步長。

因?yàn)闆]有什么理論參考,所以我還是選擇參考市面上的設(shè)計(jì)工具的步長變化設(shè)計(jì)。

比如 figma,zoom 落在 [100%, 200%) 的步長為 50,[200%, 500%) 則是 10 等等。

我的實(shí)現(xiàn)為:

const getStepByZoom = (zoom: number) => {
// 可用的步長列表
const steps = [1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];
// 看著 figma 的 step 變化想出的一個奇怪的規(guī)律
// 然后找出可選步長列表最近的并大于它的 step 作為最終步長
const step = 50 / zoom;
for (let i = 0, len = steps.length; i < len; i++) {
if (steps[i] >= step) return steps[i];
}
return steps[0];
};

const step = getStepByZoom(zoom);

計(jì)算范圍

這里我講解水平(x 軸)方向的情況。垂直方向同理,就不贅敘了。

首先計(jì)算出視口最左側(cè)和最右側(cè)的 x 坐標(biāo)值。

let startXInScene = viewport.x + startXInViewport / zoom; // 視口坐標(biāo)轉(zhuǎn)場景
let endXInScene = viewport.width + startYInViewport / zoom; // 視口坐標(biāo)轉(zhuǎn)場景

然后找離它們最近的落在刻度上的值。

對此,我實(shí)現(xiàn)了一個 getClosestVal 方法。

/**
* 找出離 value 最近的 segment 的倍數(shù)值
*/
const getClosestVal = (value: number, segment: number) => {
const n = Math.floor(value / segment);
const left = segment * n;
const right = segment * (n + 1);
return value - left <= right - value ? left : right;
};

startXInScene = getClosestVal(startXInScene, step);
endXInScene = getClosestVal(endXInScene, step);

得到起點(diǎn)和終點(diǎn),我們可以開始循環(huán)了,從 startXInScene 開始,每次循環(huán)加一個 step,直至達(dá)到末尾為止。

ctx.textAlign = 'center'; // 文字水平居中對齊

while (startXInScene <= endXInScene) {
ctx.strokeStyle = setting.rulerMarkStroke;
ctx.fillStyle = setting.rulerMarkStroke;
// 場景轉(zhuǎn)回視口再繪制??潭染€不能直接在場景中繪制,因?yàn)榭s放變換會導(dǎo)致線的粗細(xì)變化
const x = (startXInScene - viewport.x) * zoom;
// 繪制刻度
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y + setting.rulerMarkSize);
ctx.stroke();
ctx.closePath();
// 刻度值則用場景坐標(biāo)的值
ctx.fillText(String(startXInScene), x, y - 4);
// +step,指針移動
startXInScene += step;
}

垂直方向的標(biāo)尺同理,只是稍微特殊的是刻度值文字需要多做一個 -90 度的旋轉(zhuǎn)。

export const rotateInCanvas = (
ctx: CanvasRenderingContext2D,
angle: number,
cx: number,
cy: number
) => {
ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.translate(-cx, -cy);
};

rotateInCanvas(ctx, -HALF_PI, x, y);

繪制順序

繪制順序需要注意一下,先后順序?yàn)椋?/p>

  1. 繪制兩個標(biāo)尺的背景色;
  2. 繪制刻度值;
  3. 用一個和背景色同色的矩形蓋掉左上角那個方形,那個地方不能有刻度值,不如兩個標(biāo)尺的刻度會重疊。你也可以在繪制刻度值時(shí),用裁切(ctx.clip)不讓繪制到那個方形區(qū)域上;
  4. 繪制兩條分割線;

最后

標(biāo)尺實(shí)現(xiàn)大致如此,并不復(fù)雜。


當(dāng)前標(biāo)題:圖形編輯器:標(biāo)尺功能的實(shí)現(xiàn)
網(wǎng)站路徑:http://m.5511xx.com/article/cohsjdd.html