出處: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)方法
md_30d36403.png
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>