可视化:使用canvas模拟雷达设备

前言:最近写无聊的业务,都没啥值得写的东西。这个业务稍微动了点脑子,值得一写,遂有此文。

业务描述

某单位有一海上雷达设备用于采集信息,两种型号,一种扫描半径在30km内的,一种扫描半径在60km内的,隔一段时间就会传输采集到的信息入库。雷达设备放置的位置处于中心点,采集到的数据表示位置为经纬度。这里的需求是需要将点显示出来,同时点击查看雷达数据的详细内容。

效果

请添加图片描述

圆(公里的表示)

centerPixel 为当前容器的中心像素
scaleRatio 为缩放比例。
之后不再赘述

	drawCircle() {
        let ctx = this.ctx
        let centerPixel = this.centerPixel
        // 以更小的作处理. 确保 是 当前容器的最大正圆.
        let min = Math.min(centerPixel[0], centerPixel[1])
        let radius = min * this.scaleRatio;
        let diff = radius / 6;
        const PI2 = Math.PI * 2
        ctx.strokeStyle = '#31474c'
        ctx.beginPath()
        ctx.arc(centerPixel[0], centerPixel[1], radius, 0, PI2)
        ctx.stroke()
        // 在大圆的基础下 往内收缩.
        for (let i = 1; i < 7; i++) {
            ctx.beginPath()
            if(i == 3 || i == 6){
                ctx.strokeStyle = '#fff'
            }
            ctx.arc(centerPixel[0], centerPixel[1], diff * i, 0, PI2)
            ctx.stroke()
            if(i == 3 || i == 6){
                ctx.strokeStyle = '#31474c'
            }
        }
    }

经纬度坐标的处理

在这里,需要重点解决的是:获取从经纬度坐标 相对于中心点的 距离,通过此距离 处理成 该显示的屏幕坐标。应该表述已经足够清晰,可配合代码理解

	calculateCurMeterPixel(currentLonLat) {
        //  绘制 以 originlonlatCenter 作为原点的 坐标系下的 点
        let originCenter = this.originCenter
        let centerPixel = this.centerPixel
        let min = Math.min(centerPixel[0], centerPixel[1])
        // 总厂
        let allLen = 60
        // 将当前中心点 偏移回原点.  得到偏移量
        let offset = [-originCenter[0], -originCenter[1]]
        let xy = this.vector2Add(offset, currentLonLat)
        // degree =  meter / (Math.PI * 6371004) * 180; 
        //在经线上,纬度每差1度,实地距离大约为111千米;
        // 在纬线上,经度每差1度,实际距离为111×cosθ千米。(其中θ表示该纬线的纬度.在不同纬线上,经度每差1度的实际距离是不相等的)。
        // 但是这些我都不管QAQ 我就用111 ,甲方要是发现了 我再改
        // to meter  
        // const degree = (Math.PI * 6371004)
        const lonK = 111
        // 求得比例.
        // 实际意义:xRatio 为表示 当前经纬度的点 距离中心点 的偏移比例 
        // 公式的含义: x * lonK / allLen 表示 当前偏移量所代表的实地距离 在 总长距离 下的比例.
        let xRatio = xy[0] * lonK / allLen * this.scaleRatio
        let yRatio = xy[1] * lonK / allLen * this.scaleRatio
        let xValue = min * xRatio + centerPixel[0]
        console.log(xRatio, yRatio);
        // 取反的原因:
        // canvas 坐标系 向下增加.   经纬度表示 y 轴向上增加.
        let yValue = centerPixel[1] - min * yRatio
        return {
            x: xValue,
            y: yValue
        }
    }
    vector2Add(v1, v2) {
        return [v1[0] + v2[0], v1[1] + v2[1]]
    }
	

接着绘制点,在这个地方我们维护一个雷达对象的表示数组,里面包含:xy 屏幕像素,经纬度,与其他数据。

	/**
     * 绘制当前坐标系下的 点 .
     */
    drawCurrentAxisPoint() {
        let pointsArr = this.pointsArr
        this.ctx.fillStyle = '#d3cfa1'
        for (let i = 0; i < pointsArr.length; i++) {
            let cur = pointsArr[i]
            let pointStruct = this.calculateCurMeterPixel(cur)
            let info = this.calculateDirectionAndDistance(cur)
            let obj = {
                x: pointStruct.x,
                y: pointStruct.y,
                lon: cur[0],
                lat: cur[1],
                radarBatchNum: this.radarData[i].radarBatchNum,
                direction: info.direction,
                distance: info.distance,
                collectTime: this.radarData[i].collectTime,
                course: this.radarData[i].course,
                speed: this.radarData[i].speed
            }
            this.pointStructs.push(obj)
            this.drawPointByPixel(obj)
        }
        this.ctx.font = "14px Arial"
        this.ctx.textBaseline = "bottom"
        this.ctx.textAlign = "left";
        this.ctx.fillStyle = '#d3cfa1'
        for (let i = 0; i < this.pointStructs.length; i++) {
            let cur = this.pointStructs[i]
            this.ctx.fillText(this.radarData[i].radarBatchNum, cur.x, cur.y)
        }
    }
    drawPointByPixel(point) {
        let ctx = this.ctx
        ctx.beginPath()
        ctx.arc(point.x, point.y, 6, 0, Math.PI * 2)
        ctx.fill()
    }

鼠标事件处理

hover改个鼠标指针,不值一提,直接click,以此触类旁通

	/**
     * 为点生成点击事件.
     */
    clickHandler(e) {
        let x = (e.clientX - this.canvas.getBoundingClientRect().left) 
        let y = (e.clientY - this.canvas.getBoundingClientRect().top) 
        let info = this.checkPixelIsPoint({ x, y })
        if (info.isPoint) {
            console.log(info.pointStruct);
            this.tooltipDom.style.top = y + 'px'
            this.tooltipDom.style.left = x + 'px'
            this.tooltipDom.innerHTML = `
                <span>经纬度:${info.pointStruct.lon},${info.pointStruct.lat}</span><br />
                <span>时间:${info.pointStruct.collectTime}</span><br />
                <span>方位:${info.pointStruct.direction}°</span><br />
                <span>距离:${info.pointStruct.distance}km</span><br />
                <span>速度:${info.pointStruct.speed}节</span><br />
                <span>航向:${info.pointStruct.course}°</span><br />
            `
            this.tooltipDom.style.display = 'block'
            // console.log(info.pointStruct);
        }
    }
    // 检测当前坐标是否为处理点
    checkPixelIsPoint(pointStruct) {
        //  点本身 具有大小, 会影响结果. 因此 应该是计算 该pixel 落入的范围.
        //  这里做的检测机制为 包裹住圆点 的正方形盒子. 不完全精确 但应该够用.
        // 目前为写死的 半径为6的小圆点.
        let radius = 6
        let pointStructs = this.pointStructs
        let checkObj = {
            isPoint: false,
            pointStruct: null
        }
        let centerPixel = this.centerPixel
        let min = Math.min(centerPixel[0], centerPixel[1])
        for (let i = 0; i < pointStructs.length; i++) {
            let element = pointStructs[i]
            let leftBottom = [element.x - radius, element.y - radius];
            let rightTop = [element.x + radius, element.y + radius];
            // 在范围内. 说明 该点 命中 检测.
            if ((leftBottom[0] < pointStruct.x && pointStruct.x < rightTop[0]) && (leftBottom[1] < pointStruct.y && pointStruct.y < rightTop[1])) {
                checkObj.pointStruct = element
                checkObj.isPoint = true
                return checkObj
            }
        }
        return checkObj
    }

tooltipDom 的创建 这里fatherDom 表示将这个雷达塞入到哪个DOM下去。

	/**
     * 创建 图例.. 即点击弹窗后的 div 内容
     */
    createTooltip() {
        // 这里仅做创建. 因为无论是 输出的文本内容或者是 位置. 都需要配合点击事件 做处理.
        let dom = document.createElement('div')
        dom.style.display = 'none'
        dom.style.color = 'red'
        dom.style.fontSize = '20px'
        dom.style.position = 'absolute'
        dom.style.width = '500px'
        dom.style.pointerEvents = 'none'
        let fatherDom = document.getElementById(this.radarId)
        fatherDom.style.position = 'relative'
        fatherDom.appendChild(dom)
        return dom
    }

完整代码

使用示例

let radarInstance = new Radar({
          pointsArr: [[120.61, 24], [100.61, -9]],
          originCenter: [114.61, 14],
          radarData: data
});
/**
 * canvas 绘制雷达图\
 * 需求一个雷达图 同时 需要 填充点、点击事件
 * @written by liuqingQAQ on 2022/08/15.
 */

const defaultOptions = {
    radarId: 'radar',
    originCenter: [114.61, 14],
    pointsArr: [[120.61, 24], [100.61, -9]]
}

export default class Radar {
    constructor(options) {

        let __options = Object.assign(defaultOptions, options);

        this.radarId = __options.radarId

        this.originCenter = __options.originCenter

        this.pointsArr = __options.pointsArr

        this.radarData = __options.radarData

        console.log(this.originCenter, this.pointsArr);

        this.pointStructs = []

        this.scaleRatio = 0.9

        this.createCanvas()

        this.tooltipDom = this.createTooltip()

        this.render()

        // console.log(this);
    }

    destroy() {
        let dom = document.getElementById(this.radarId)
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
        dom.removeChild(this.canvas)
        // tooltipDom 也要清除.
        dom.removeChild(this.tooltipDom)
        this.canvas = null;
    }

    /**
     * 创建 图例.. 即点击弹窗后的 div 内容
     */
    createTooltip() {
        // 这里仅做创建. 因为无论是 输出的文本内容或者是 位置. 都需要配合点击事件 做处理.
        let dom = document.createElement('div')
        dom.style.display = 'none'
        dom.style.color = 'red'
        dom.style.fontSize = '20px'
        dom.style.position = 'absolute'
        dom.style.width = '500px'
        dom.style.pointerEvents = 'none'
        let fatherDom = document.getElementById(this.radarId)
        fatherDom.style.position = 'relative'
        fatherDom.appendChild(dom)
        return dom
    }

    createCanvas() {
        let dom = document.getElementById(this.radarId)
        let canvas = document.createElement('canvas')
        let ctx = canvas.getContext('2d')
        canvas.height = dom.clientHeight
        canvas.width = dom.clientWidth
        canvas.onmousemove = this.hoverHandler.bind(this)
        canvas.onclick = this.clickHandler.bind(this)
        dom.appendChild(canvas)
        this.centerPixel = this.calculateCenterPixel(dom.clientHeight, dom.clientWidth)
        this.canvas = canvas
        this.ctx = ctx

        // 作为离屏 使用. 保存住 不会变的 点线 的图像状态
        let screenCanvas = document.createElement('canvas')
        let screenCtx = screenCanvas.getContext('2d')
        screenCanvas.height = canvas.height
        screenCanvas.width = canvas.width
        this.screenCanvas = screenCanvas
        this.screenCtx = screenCtx
    }

    /**
     * 计算中心像素
     * @param {*} height clientHeight
     * @param {*} width clientWidth
     * @returns [x,y]
     */
    calculateCenterPixel(height, width) {
        return [Math.round(width / 2), Math.round(height / 2)]
    }

    render() {
        this.renderBase()
        this.renderPoint()
        let imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
        this.screenCtx.putImageData(imageData, 0, 0)

        this.startRadarSaomiao()
    }

    // haha .. lande fanyi 
    startRadarSaomiao() {
        let deg = 0;
        let _this = this
        function animate() {
            deg++;
            _this.ctx.drawImage(_this.screenCanvas, 0, 0)
            _this.cover()
            _this.drawRadarPie(deg)
            requestAnimationFrame(animate)
        }
        animate()
    }

    /**
     * 创建底图
     */
    renderBase() {
        this.drawCircle()
        this.drawText()
        this.drawLine()
    }

    cover() {
        let centerPixel = this.centerPixel
        let ctx = this.ctx
        let min = Math.min(centerPixel[0], centerPixel[1])
        ctx.fillStyle = 'rgba(5,21,49,0.07)';
        ctx.arc(centerPixel[0], centerPixel[1], min * this.scaleRatio, 0, 2 * Math.PI);
        ctx.fill();
        ctx.restore();
    }

    // 动画绘制.. 从最基础的 绘制扇形开始
    drawRadarPie(iDeg) {
        let centerPixel = this.centerPixel
        let min = Math.min(centerPixel[0], centerPixel[1])
        let ctx = this.ctx
        ctx.fillStyle = 'rgba(117,253,159,.2)';
        ctx.beginPath();
        ctx.moveTo(centerPixel[0], centerPixel[1]);
        ctx.arc(centerPixel[0], centerPixel[1], min * this.scaleRatio, (-2 * 20 + iDeg) / 180 * Math.PI, (0 + iDeg) / 180 * Math.PI);
        ctx.closePath();
        ctx.fill();
    }

    drawCircle() {
        let ctx = this.ctx
        let centerPixel = this.centerPixel
        // 以更小的作处理. 确保 是 当前容器的最大正圆.
        let min = Math.min(centerPixel[0], centerPixel[1])
        let radius = min * this.scaleRatio;
        let diff = radius / 6;
        const PI2 = Math.PI * 2
        ctx.strokeStyle = '#31474c'
        ctx.beginPath()
        ctx.arc(centerPixel[0], centerPixel[1], radius, 0, PI2)
        ctx.stroke()
        // 在大圆的基础下 往内收缩.
        for (let i = 1; i < 7; i++) {
            ctx.beginPath()
            if(i == 3 || i == 6){
                ctx.strokeStyle = '#fff'
            }
            ctx.arc(centerPixel[0], centerPixel[1], diff * i, 0, PI2)
            ctx.stroke()
            if(i == 3 || i == 6){
                ctx.strokeStyle = '#31474c'
            }
        }
    }

    drawText() {
        let ctx = this.ctx
        let centerPixel = this.centerPixel
        // 以更小的作处理. 确保 是 当前容器的最大正圆.
        let min = Math.min(centerPixel[0], centerPixel[1])
        let radius = min * this.scaleRatio;
        let diff = radius / 6;
        const PI2 = Math.PI * 2
        let textArr = [0, 10, 20, 30, 40, 50, 60]
        ctx.font = "20px Arial";
        ctx.fillStyle = '#fff'
        // 在大圆的基础下 往内收缩.
        for (let i = 1; i < 7; i++) {
            ctx.arc(centerPixel[0], centerPixel[1], diff * i, 0, PI2)
            
            ctx.fillText(textArr[i], centerPixel[0] + diff * i, centerPixel[1] - diff / 10);
        }
        ctx.textAlign = "right";
        for (let i = 1; i < 7; i++) {
            ctx.arc(centerPixel[0], centerPixel[1], diff * i, 0, PI2)
            ctx.fillText(textArr[i], centerPixel[0] - diff * i, centerPixel[1] - diff / 10);
        }
    }

    drawLine() {
        let ctx = this.ctx
        let centerPixel = this.centerPixel
        // 以更小的作处理. 确保 是 当前容器的最大正圆.
        let min = Math.min(centerPixel[0], centerPixel[1])
        let radius = min * this.scaleRatio;
        let diff = radius / 4;
        const PI2 = Math.PI * 2
        // 绘制 十字线 先横后树.
        ctx.strokeStyle = '#e8dfe1'
        ctx.beginPath()
        ctx.moveTo(centerPixel[0] - radius, centerPixel[1])
        ctx.lineTo(centerPixel[0] + radius, centerPixel[1])
        ctx.stroke();
        ctx.closePath()
        ctx.beginPath()
        ctx.moveTo(centerPixel[0], centerPixel[1] - radius)
        ctx.lineTo(centerPixel[0], centerPixel[1] + radius)
        ctx.stroke();
        ctx.closePath()
    }

    /**
     * 渲染点.. 同时记录
     */
    renderPoint() {
        this.drawCenterPoint()
        this.drawCurrentAxisPoint()
    }

    /**
     * Draw the center point
     */
    drawCenterPoint() {
        let ctx = this.ctx
        let centerPixel = this.centerPixel
        // 以更小的作处理. 确保 是 当前容器的最大正圆.
        let min = Math.min(centerPixel[0], centerPixel[1])
        let radius = min * this.scaleRatio;
        let diff = radius / 5;
        const PI2 = Math.PI * 2
        let pointSize = min / 100;
        ctx.beginPath()
        ctx.fillStyle = '#fff'
        ctx.arc(centerPixel[0], centerPixel[1], pointSize, 0, PI2)
        ctx.fill()
    }

    /**
     * 绘制当前坐标系下的 点 .
     */
    drawCurrentAxisPoint() {
        let pointsArr = this.pointsArr
        this.ctx.fillStyle = '#d3cfa1'
        for (let i = 0; i < pointsArr.length; i++) {
            let cur = pointsArr[i]
            let pointStruct = this.calculateCurMeterPixel(cur)
            let info = this.calculateDirectionAndDistance(cur)
            let obj = {
                x: pointStruct.x,
                y: pointStruct.y,
                lon: cur[0],
                lat: cur[1],
                radarBatchNum: this.radarData[i].radarBatchNum,
                direction: info.direction,
                distance: info.distance,
                collectTime: this.radarData[i].collectTime,
                course: this.radarData[i].course,
                speed: this.radarData[i].speed
            }
            this.pointStructs.push(obj)
            this.drawPointByPixel(obj)
        }
        this.ctx.font = "14px Arial"
        this.ctx.textBaseline = "bottom"
        this.ctx.textAlign = "left";
        this.ctx.fillStyle = '#d3cfa1'
        for (let i = 0; i < this.pointStructs.length; i++) {
            let cur = this.pointStructs[i]
            this.ctx.fillText(this.radarData[i].radarBatchNum, cur.x, cur.y)
        }
    }

    // 正切tan转换角度
    getTanDeg(tan) {
        var result = Math.atan(tan) / (Math.PI / 180);
        result = Math.round(result);
        return result;
    }

    // 计算角度
    ComputingAngle(x1, y1, x2, y2) {
        let computedNum
        let numDeg = this.getTanDeg(Math.abs((y1 - y2) / (x1 - x2)))
        if (x1 - x2 < 0 && y1 - y2 < 0) {
            computedNum = 90 - numDeg
        }
        else if (x1 - x2 > 0 && y1 - y2 < 0) {
            computedNum = 270 + numDeg
        }
        else if (x1 - x2 > 0 && y1 - y2 > 0) {
            computedNum = 270 - numDeg
        }
        else if (x1 - x2 < 0 && y1 - y2 > 0) {
            computedNum = 90 + numDeg
        }
        else if (x1 - x2 == 0) {
            if (y1 - y2 < 0) {
                computedNum = 0
            } else if (y1 - y2 > 0) {
                computedNum = 180
            }
        }
        else if (y1 - y2 == 0) {
            if (x1 - x2 < 0) {
                computedNum = 90
            } else if (x1 - x2 > 0) {
                computedNum = 270
            }
        }
        return computedNum;
    }

    // 计算方位 跟距离.
    calculateDirectionAndDistance(cur) {
        let originCenter = this.originCenter
        // 将当前中心点 偏移回原点.  得到偏移量
        let offset = [-originCenter[0], -originCenter[1]]
        let xy = this.vector2Add(offset, cur)
        let direction = this.ComputingAngle(originCenter[0], originCenter[1], cur[0], cur[1])
        let distance = Math.pow(Math.pow(xy[0] * 111, 2) + Math.pow(xy[1] * 111, 2), 0.5).toFixed(2)
        return {
            direction,
            distance
        }
    }

    /**
     * 根据原始点计算 该渲染的canvas像素坐标
     * @param {*} currentLonLat 经纬度数组
     */
    calculateCurLonlatPixel(currentLonLat) {
        //  绘制 以 originlonlatCenter 作为原点的 坐标系下的 点
        let originCenter = this.originCenter
        let centerPixel = this.centerPixel
        let min = Math.min(centerPixel[0], centerPixel[1])
        // 总厂
        let allLen = 2
        // 将当前中心点 偏移回原点.  得到偏移量
        let offset = [-originCenter[0], -originCenter[1]]
        let xy = this.vector2Add(offset, currentLonLat)
        let xRatio = xy[0] / allLen * this.scaleRatio
        let yRatio = xy[1] / allLen * this.scaleRatio
        let xValue = min * xRatio + centerPixel[0]
        // 取反的原因:
        // canvas 坐标系 向下增加.   经纬度表示 y 轴向上增加.
        let yValue = centerPixel[1] - min * yRatio
        return {
            x: xValue,
            y: yValue
        }
    }

    calculateCurMeterPixel(currentLonLat) {
        //  绘制 以 originlonlatCenter 作为原点的 坐标系下的 点
        let originCenter = this.originCenter
        let centerPixel = this.centerPixel
        let min = Math.min(centerPixel[0], centerPixel[1])
        // 总厂
        let allLen = 60
        // 将当前中心点 偏移回原点.  得到偏移量
        let offset = [-originCenter[0], -originCenter[1]]
        let xy = this.vector2Add(offset, currentLonLat)
        // degree =  meter / (Math.PI * 6371004) * 180; 
        //在经线上,纬度每差1度,实地距离大约为111千米;
        // 在纬线上,经度每差1度,实际距离为111×cosθ千米。(其中θ表示该纬线的纬度.在不同纬线上,经度每差1度的实际距离是不相等的)。
        // to meter  
        // const degree = (Math.PI * 6371004)
        const lonK = 111
        // 求得比例.
        let xRatio = xy[0] * lonK / allLen * this.scaleRatio
        let yRatio = xy[1] * lonK / allLen * this.scaleRatio
        let xValue = min * xRatio + centerPixel[0]
        console.log(xRatio, yRatio);
        // 取反的原因:
        // canvas 坐标系 向下增加.   经纬度表示 y 轴向上增加.
        let yValue = centerPixel[1] - min * yRatio
        return {
            x: xValue,
            y: yValue
        }
    }

    drawPointByPixel(point) {
        let ctx = this.ctx
        ctx.beginPath()
        ctx.arc(point.x, point.y, 6, 0, Math.PI * 2)
        ctx.fill()
    }

    vector2Add(v1, v2) {
        return [v1[0] + v2[0], v1[1] + v2[1]]
    }

    /**
     * 为点生成点击事件.
     */
    clickHandler(e) {
        let x = (e.clientX - this.canvas.getBoundingClientRect().left) / __XRATIO
        let y = (e.clientY - this.canvas.getBoundingClientRect().top) / __YRATIO
        let info = this.checkPixelIsPoint({ x, y })
        if (info.isPoint) {
            console.log(info.pointStruct);
            this.tooltipDom.style.top = y + 'px'
            this.tooltipDom.style.left = x + 'px'
            this.tooltipDom.innerHTML = `
                <span>经纬度:${info.pointStruct.lon},${info.pointStruct.lat}</span><br />
                <span>时间:${info.pointStruct.collectTime}</span><br />
                <span>方位:${info.pointStruct.direction}°</span><br />
                <span>距离:${info.pointStruct.distance}km</span><br />
                <span>速度:${info.pointStruct.speed}节</span><br />
                <span>航向:${info.pointStruct.course}°</span><br />
            `
            this.tooltipDom.style.display = 'block'
            // console.log(info.pointStruct);
        }
    }

    hoverHandler(e) {
        let x = (e.clientX - this.canvas.getBoundingClientRect().left) / __XRATIO
        let y = (e.clientY - this.canvas.getBoundingClientRect().top) / __YRATIO
        let info = this.checkPixelIsPoint({ x, y })
        var body = document.querySelector("body")
        if (info.isPoint) {
            body.style.cursor = "pointer"
        } else {
            body.style.cursor = ""
        }
    }

    // 检测当前坐标是否为处理点
    checkPixelIsPoint(pointStruct) {
        //  点本身 具有大小, 会影响结果. 因此 应该是计算 该pixel 落入的范围.
        //  这里做的检测机制为 包裹住圆点 的正方形盒子. 不完全精确 但应该够用.
        // 目前为写死的 半径为4的小圆点.
        let radius = 6
        let pointStructs = this.pointStructs
        let checkObj = {
            isPoint: false,
            pointStruct: null
        }
        let centerPixel = this.centerPixel
        let min = Math.min(centerPixel[0], centerPixel[1])
        let pointSize = min / 100;
        for (let i = 0; i < pointStructs.length; i++) {
            let element = pointStructs[i]
            let leftBottom = [element.x - radius, element.y - radius];
            let rightTop = [element.x + radius, element.y + radius];
            // 在范围内. 说明 该点 命中 检测.
            if ((leftBottom[0] < pointStruct.x && pointStruct.x < rightTop[0]) && (leftBottom[1] < pointStruct.y && pointStruct.y < rightTop[1])) {
                checkObj.pointStruct = element
                checkObj.isPoint = true
                return checkObj
            }
        }
        return checkObj
    }

    // 
    containsExtent(pixel) {
        console.log();
    }

}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值