Mapbox Sprite精靈圖生成

出處:ATtuing - 博客園 肥橙,https://www.cnblogs.com/ATtuing/p/9273391.html

1.什么是sprite文件

sprite 文件主要是將一堆小圖生成一種大圖的方法弊添,并且將每張小圖的位置信息保存下來秩命,方便讀取绩聘。在網(wǎng)絡(luò)請求中會減少請求的數(shù)量,mapbox借鑒前端中CSS Sprite方法存儲圖標(biāo)信息的稻薇。sprite.png文件保存圖標(biāo)况毅,sprite.json保存名稱及位置信息,下圖圖展示的是小圖標(biāo)與大圖文件的示例异吻。下面我講一下兩種文件轉(zhuǎn)換裹赴。

md_beee6768.png

轉(zhuǎn)為

md_d949a4ef.png

2.實(shí)現(xiàn)的功能

此基礎(chǔ)上將小圖轉(zhuǎn)大圖功能用JavaScript實(shí)現(xiàn)。使用Vue诀浪、Element實(shí)現(xiàn)棋返。

演示地址:https://c317.gitee.io/myb_style/html/creat_MBSprite.html

md_30d36403.png
3.具體實(shí)現(xiàn)方法

1.js獲取圖片像素

function getXY(canvas, x, y) {
                let ctx = canvas.getContext("2d");
                // 獲取畫布上的圖像像素矩陣
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let w = imageData.width
                let data = imageData.data
                let color = []
                color[0] = data[(y * w + x) * 4]
                color[1] = data[(y * w + x) * 4 + 1]
                color[2] = data[(y * w + x) * 4 + 2]
                color[3] = data[(y * w + x) * 4 + 3]
                return color
            }

2.js設(shè)置圖片像素

//創(chuàng)建canvas
let editMap = document.createElement('canvas');
editMap.width = allwidth;//設(shè)置寬度
editMap.height = allheight;//設(shè)置高度
let editCxt = editMap.getContext("2d");
//獲取ImageData
let imageData = editCxt.getImageData(0, 0, allwidth, allheight);
function setXY(imageData, x, y, color) {
                let w = imageData.width
                let data = imageData.data
                data[(y * w + x) * 4] = color[0]
                data[(y * w + x) * 4 + 1] = color[1]
                data[(y * w + x) * 4 + 2] = color[2]
                data[(y * w + x) * 4 + 3] = color[3]
                imageData.data = data;
            }

3.小圖轉(zhuǎn)大圖

將小圖標(biāo)合成一張sprite大圖并在sprite.json中記錄生成的位置信息,這里最主要的就是圖標(biāo)的擺放規(guī)則笋妥。

(1)獲取所有的圖標(biāo)文件懊昨,按照高度從小到大排列

(2)根據(jù)大圖生成的默認(rèn)寬度,循環(huán)小圖片春宣,形成一行一行的圖片集合酵颁。

(3)根據(jù)行數(shù)和寬度生成大圖的寬度。

(4)循環(huán)小圖標(biāo)月帝,在大圖中畫出小圖標(biāo)躏惋,并記錄位置信息。

實(shí)現(xiàn)成果與核心代碼如下:

md_c076a122.png
function creatSprite(paramlist) {
                //圖片默認(rèn)寬度為255
                let allwidth = 255;
                let rowparams = [], paramnowlist = [];
                let countnum = 0;
                for (let i = 0; i < paramlist.length; i++) {
                    countnum += paramlist[i].width;
                    if (countnum > allwidth) {
                        i = i - 1;
                        countnum = 0;
                        rowparams.push(paramnowlist);
                        paramnowlist = [];
                    } else {
                        paramnowlist.push(paramlist[i]);
                    }
                    if (i === paramlist.length - 1) {
                        rowparams.push(paramnowlist);
                        break;
                    }
                }
                //計算應(yīng)有的高度
                let allheight = 0;
                rowparams.forEach(item => {
                    allheight += Math.max.apply(Math, item.map(m => m.height));
                })
                //計算應(yīng)有的寬度
                allwidth = 0
                rowparams[0].forEach(item => {
                    allwidth += item.width;
                })
                if (allwidth > 200) allwidth = 255;
                console.log(allwidth)
                let spritejson = "{\n";
                //開始畫大圖
                let editMap = document.createElement('canvas');
                editMap.width = allwidth;
                editMap.height = allheight;
                let editCxt = editMap.getContext("2d");
                let editImageData = editCxt.getImageData(0, 0, allwidth, allheight);
                //保存起始高度
                let heighttemp = 0;
                for (let i = 0; i < rowparams.length; i++) {
                    let tempwidthnum = 0;
                    for (let j = 0; j < rowparams[i].length; j++) {
                        let map = rowparams[i][j].canvas;
                        //循環(huán)小圖片
                        for (let x = 0; x < map.width; x++) {
                            for (let y = 0; y < map.height; y++) {
                                //獲取像素
                                let color = this.getXY(map, x, y);
                                this.setXY(editImageData, x + tempwidthnum, y + heighttemp, color);
                            }
                        }
                        spritejson += "  \"" + rowparams[i][j].name.replace("-", "/").replace("&", ":") + "\":{\"x\":";
                        spritejson += tempwidthnum + ",\"y\":" + heighttemp + ",\"width\":" + rowparams[i][j].width;
                        spritejson += ",\"height\":" + rowparams[i][j].height + ",\"pixelRatio\":1,\"sdf\":false},\n";
                        //增加寬度
                        tempwidthnum += rowparams[i][j].width;
                    }
                    heighttemp += Math.max.apply(Math, rowparams[i].map(m => m.height));
                }
                //保存大圖
                editCxt.putImageData(editImageData, 0, 0);
                this.editURL = editMap.toDataURL("image/png");//取得圖像的數(shù)據(jù)URI

                spritejson = spritejson.substring(0, spritejson.lastIndexOf(','));
                spritejson += "\n}";
                this.spritejson = spritejson
            }

4.完整代碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>生成Mapbox Sprite(精靈圖)</title>
    <!-- import CSS -->
    <link  rel="stylesheet">
    <style>
        html, body, #app{
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            position: absolute;
        }
        .upload, .creat{
            border-radius: 4px;
            background: #d3dce6;
            height: 100%;
        }
        .but{
            height: 100%;
            display: flex;
            align-items:center;
            justify-content:center;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row style="height: 100%">
        <el-col class="upload" :span="11">
            <el-upload
                    action="https://jsonplaceholder.typicode.com/posts/"
                    list-type="picture-card"
                    accept="image/*"
                    :on-preview="handlePreview"
                    :on-success="handleSuccess"
                    :on-remove="handleRemove" multiple>
                <i class="el-icon-plus"></i>
                <div slot="tip" class="el-upload__tip">只能上傳圖片格式文件嚷辅,且不超過500kb</div>
            </el-upload>
            <el-dialog :visible.sync="dialogVisible">
                <img width="100%" :src="dialogImageUrl" alt="">
            </el-dialog>
        </el-col>
        <el-col class="but" :span="2">
            <el-button type="primary" @click="image">轉(zhuǎn)換</el-button>
        </el-col>
        <el-col class="creat" :span="11">
            <el-row style="height: 45%">
                <img :src="editURL" style="border:1px solid #6f6f6f">
            </el-row>
            <el-row style="height: 10%">
                <el-button type="primary" @click="downloadImg">下載圖片</el-button>
                <el-button type="primary" @click="downloadJSON">下載JSON</el-button>
            </el-row>
            <el-row style="height: 45%">
                <el-input
                    type="textarea"
                    :rows="18"
                    placeholder="JSON內(nèi)容"
                    v-model="spritejson"></el-input>
            </el-row>
        </el-col>
    </el-row>
</div>
</body>
<!-- import Vue before Element -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://cdn.bootcss.com/element-ui/2.4.5/index.js"></script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                dialogImageUrl: '',
                dialogVisible: false,
                disabled: false,
                fileList: [],
                canvas: [],
                paramList: [],//List<Param>
                rowparams: [],//List<List<Param>>
                editURL:'',
                spritejson:''
            }
        },
        mounted() {
        },
        methods: {
            handleRemove(file, fileList) {
                this.fileList = fileList;
            },
            handlePreview(file) {
                this.dialogImageUrl = file.url;
                this.dialogVisible = true;
            },
            handleSuccess(response, file, fileList) {
                this.fileList = fileList;
            },
            image() {
                let paramlist = [];
                if (this.fileList.length === 0) return;
                this.fileList.forEach(file => {
                    let image = new Image();
                    image.src = file.url;
                    let canvas = document.createElement('canvas');
                    canvas.width = image.width;
                    canvas.height = image.height;
                    canvas.getContext("2d").drawImage(image, 0, 0);
                    paramlist.push({
                        name: file.name,
                        x: 0, y: 0,
                        width: image.width,
                        height: image.height,
                        canvas: canvas
                    })
                })
                paramlist.sort(function (a, b) {
                    return a.height - b.height
                })
                this.paramList = paramlist;
                this.creatSprite(paramlist);
            },
            creatSprite(paramlist) {
                //圖片默認(rèn)寬度為255
                let allwidth = 255;
                let rowparams = [], paramnowlist = [];
                let countnum = 0;
                for (let i = 0; i < paramlist.length; i++) {
                    countnum += paramlist[i].width;
                    if (countnum > allwidth) {
                        i = i - 1;
                        countnum = 0;
                        rowparams.push(paramnowlist);
                        paramnowlist = [];
                    } else {
                        paramnowlist.push(paramlist[i]);
                    }
                    if (i === paramlist.length - 1) {
                        rowparams.push(paramnowlist);
                        break;
                    }
                }
                //計算應(yīng)有的高度
                let allheight = 0;
                rowparams.forEach(item => {
                    allheight += Math.max.apply(Math, item.map(m => m.height));
                })
                //計算應(yīng)有的寬度
                allwidth = 0
                rowparams[0].forEach(item => {
                    allwidth += item.width;
                })
                if (allwidth > 200) allwidth = 255;
                console.log(allwidth)
                let spritejson = "{\n";
                //開始畫大圖
                let editMap = document.createElement('canvas');
                editMap.width = allwidth;
                editMap.height = allheight;
                let editCxt = editMap.getContext("2d");
                let editImageData = editCxt.getImageData(0, 0, allwidth, allheight);
                //保存起始高度
                let heighttemp = 0;
                for (let i = 0; i < rowparams.length; i++) {
                    let tempwidthnum = 0;
                    for (let j = 0; j < rowparams[i].length; j++) {
                        let map = rowparams[i][j].canvas;
                        //循環(huán)小圖片
                        for (let x = 0; x < map.width; x++) {
                            for (let y = 0; y < map.height; y++) {
                                //獲取像素
                                let color = this.getXY(map, x, y);
                                this.setXY(editImageData, x + tempwidthnum, y + heighttemp, color);
                            }
                        }
                        spritejson += "  \"" + rowparams[i][j].name.replace("-", "/").replace("&", ":") + "\":{\"x\":";
                        spritejson += tempwidthnum + ",\"y\":" + heighttemp + ",\"width\":" + rowparams[i][j].width;
                        spritejson += ",\"height\":" + rowparams[i][j].height + ",\"pixelRatio\":1,\"sdf\":false},\n";
                        //增加寬度
                        tempwidthnum += rowparams[i][j].width;
                    }
                    heighttemp += Math.max.apply(Math, rowparams[i].map(m => m.height));
                }
                //保存大圖
                editCxt.putImageData(editImageData, 0, 0);
                this.editURL = editMap.toDataURL("image/png");//取得圖像的數(shù)據(jù)URI

                spritejson = spritejson.substring(0, spritejson.lastIndexOf(','));
                spritejson += "\n}";
                this.spritejson = spritejson
            },
            getXY(canvas, x, y) {
                let ctx = canvas.getContext("2d");
                // 獲取畫布上的圖像像素矩陣
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let w = imageData.width
                let data = imageData.data
                let color = []
                color[0] = data[(y * w + x) * 4]
                color[1] = data[(y * w + x) * 4 + 1]
                color[2] = data[(y * w + x) * 4 + 2]
                color[3] = data[(y * w + x) * 4 + 3]
                return color
            },
            setXY(imageData, x, y, color) {
                let w = imageData.width
                let data = imageData.data
                data[(y * w + x) * 4] = color[0]
                data[(y * w + x) * 4 + 1] = color[1]
                data[(y * w + x) * 4 + 2] = color[2]
                data[(y * w + x) * 4 + 3] = color[3]
                imageData.data = data;
            },
            downloadImg() {
                if (this.editURL === null || this.editURL === '') return;
                if (this.spritejson === null || this.spritejson === '') return;
                // 將圖片的src屬性作為URL地址
                let a = document.createElement('a')
                let event = new MouseEvent('click')
                a.download = 'sprite'
                a.href = this.editURL
                a.dispatchEvent(event);
            },
            downloadJSON(){
                let pom = document.createElement('a');
                pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.spritejson));
                pom.setAttribute('download', 'sprite.json');
                if (document.createEvent) {
                    let event = document.createEvent('MouseEvents');
                    event.initEvent('click', true, true);
                    pom.dispatchEvent(event);
                } else {
                    pom.click();
                }
            }
        }
    })
</script>
</html>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簿姨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扁位,老刑警劉巖准潭,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異域仇,居然都是意外死亡刑然,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門暇务,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泼掠,“玉大人,你說我怎么就攤上這事垦细≡裾颍” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵括改,是天一觀的道長腻豌。 經(jīng)常有香客問我,道長叹谁,這世上最難降的妖魔是什么饲梭? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任乘盖,我火速辦了婚禮焰檩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘订框。我一直安慰自己析苫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布穿扳。 她就那樣靜靜地躺著衩侥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪矛物。 梳的紋絲不亂的頭發(fā)上茫死,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機(jī)與錄音履羞,去河邊找鬼峦萎。 笑死,一個胖子當(dāng)著我的面吹牛忆首,可吹牛的內(nèi)容都是我干的爱榔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糙及,長吁一口氣:“原來是場噩夢啊……” “哼详幽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唇聘,失蹤者是張志新(化名)和其女友劉穎版姑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迟郎,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漠酿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谎亩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炒嘲。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匈庭,靈堂內(nèi)的尸體忽然破棺而出夫凸,到底是詐尸還是另有隱情,我是刑警寧澤阱持,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布夭拌,位于F島的核電站,受9級特大地震影響衷咽,放射性物質(zhì)發(fā)生泄漏鸽扁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一镶骗、第九天 我趴在偏房一處隱蔽的房頂上張望桶现。 院中可真熱鬧,春花似錦鼎姊、人聲如沸骡和。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慰于。三九已至,卻和暖如春唤衫,著一層夾襖步出監(jiān)牢的瞬間婆赠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工佳励, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留休里,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓植兰,卻偏偏與公主長得像份帐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子楣导,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容

  • CSS雪碧废境,即CSS Sprite,也有人叫它CSS精靈,是一種CSS圖像合并技術(shù)噩凹,該方法是將小圖標(biāo)和背景圖像合并...
    ColinLiu123閱讀 834評論 0 0
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5巴元? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 27,486評論 1 45
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險厭惡者驮宴,不喜歡去冒險逮刨,但是人生放棄了冒險,也就放棄了無數(shù)的可能堵泽。 ...
    yichen大刀閱讀 6,052評論 0 4
  • 公元:2019年11月28日19時42分農(nóng)歷:二零一九年 十一月 初三日 戌時干支:己亥乙亥己巳甲戌當(dāng)月節(jié)氣:立冬...
    石放閱讀 6,879評論 0 2
  • 今天上午陪老媽看病修己,下午健身房跑步,晚上想想今天還沒有斷舍離迎罗,馬上做睬愤,衣架和旁邊的的布衣架,一看亂亂纹安,又想想自己是...
    影子3623253閱讀 2,913評論 1 8