1 | ;
|
2 |
|
3 | /**
|
4 | * 精灵图生成
|
5 | *
|
6 | * @class OutSprite
|
7 | * {
|
8 | * srcDir:'', <string> 源文件路径
|
9 | * distSpreiteDir:'', [string] 精灵图输出目录
|
10 | * distScssDir:'' [string] scss输出目录
|
11 | * }
|
12 | */
|
13 | class OutSprite{
|
14 | constructor(option){
|
15 | const _ts = this;
|
16 |
|
17 | option = option || {};
|
18 |
|
19 | let m = _ts.m = {
|
20 | path:require('path'),
|
21 | fs:require('fs-extra'),
|
22 | spritesmith:require('spritesmith'),
|
23 | svgstore:require('svgstore'),
|
24 | pathInfo:require('./getPathInfo')
|
25 | },
|
26 | config = _ts.config = {};
|
27 |
|
28 | //配置写入到_ts.config
|
29 | for(let i in option){
|
30 | config[i] = option[i];
|
31 | };
|
32 |
|
33 | return new Promise((resolve,reject)=>{
|
34 | //检查目录是否存在
|
35 | if(m.pathInfo(config.srcDir).type === 'dir'){
|
36 | _ts.init().then(v => {
|
37 | resolve(v);
|
38 | }).catch(e => {
|
39 | reject(e);
|
40 | });
|
41 | }else{
|
42 | reject({
|
43 | status:'error',
|
44 | msg:`${config.srcDir} 不是有效的目录`
|
45 | });
|
46 | };
|
47 | });
|
48 | }
|
49 |
|
50 | init(){
|
51 | const _ts = this,
|
52 | m = _ts.m,
|
53 | config = _ts.config;
|
54 |
|
55 | let srcDirName = (()=>{
|
56 | let pathDirs = config.srcDir.split(m.path.sep);
|
57 | return pathDirs[pathDirs.length-1];
|
58 | })(),
|
59 |
|
60 | spriteTaskList = [],
|
61 |
|
62 | imgList = _ts.getImgList();
|
63 |
|
64 |
|
65 | //处理svg图片
|
66 | spriteTaskList.push(new Promise((resolve,reject)=>{
|
67 | //处理svg图片
|
68 | if(imgList.svg.length){
|
69 | let svgstore = m.svgstore(),
|
70 | dist = m.path.join(config.distSpreiteDir,srcDirName+'.svg');
|
71 |
|
72 | //将svg图片添加到svgstore
|
73 | imgList.svg.forEach((item,index)=>{
|
74 | let svgElementName = m.path.basename(item,m.path.extname(item));
|
75 | svgstore.add(svgElementName,m.fs.readFileSync(item,'utf8'));
|
76 | });
|
77 |
|
78 | //写入文件
|
79 | m.fs.ensureDir(config.distSpreiteDir,err => {
|
80 | if(err){
|
81 | reject({
|
82 | status:'error',
|
83 | msg:`创建失败 ${config.distSpreiteDir}`,
|
84 | info:err
|
85 | });
|
86 | }else{
|
87 | try {
|
88 | m.fs.writeFileSync(dist,svgstore);
|
89 | resolve({
|
90 | status:'success',
|
91 | msg:`SVG精灵 ${config.srcDir}`,
|
92 | data:svgstore.toString()
|
93 | });
|
94 | } catch (err) {
|
95 | reject({
|
96 | status:'error',
|
97 | msg:`写入失败 ${dist}`,
|
98 | info:err
|
99 | });
|
100 | };
|
101 | };
|
102 | });
|
103 | }else{
|
104 | resolve({
|
105 | status:'success',
|
106 | msg:'目录无Svg图片要处理'
|
107 | });
|
108 | };
|
109 | }));
|
110 |
|
111 |
|
112 |
|
113 | //处理png精灵图片
|
114 | spriteTaskList.push(new Promise((resolve,reject)=>{
|
115 | if(imgList.png.length){
|
116 | //tl,bl
|
117 |
|
118 | //设置选项
|
119 | let option = {
|
120 | src:imgList.png, //png图片列表
|
121 | padding:4, //图片间隙大小
|
122 | algorithm:'binary-tree', //图片对齐方式
|
123 | algorithmOpts:{
|
124 | sort:false
|
125 | }
|
126 | };
|
127 |
|
128 | (()=>{
|
129 | let rule = srcDirName.split('_'),
|
130 | temp = +rule[rule.length - 1],
|
131 | isAlgorithms = (type)=>{
|
132 | let tempType = 'binary-tree';
|
133 | switch (type) {
|
134 | case 'td':
|
135 | tempType = 'top-down';
|
136 | break;
|
137 | case 'lr':
|
138 | tempType = 'left-right';
|
139 | break;
|
140 | case 'd':
|
141 | tempType = 'diagonal';
|
142 | break;
|
143 | case 'ad':
|
144 | tempType = 'binary-tree';
|
145 | break;
|
146 | };
|
147 | return tempType;
|
148 | };
|
149 |
|
150 | option.padding = isNaN(temp) ? 4 : temp;
|
151 | option.algorithm = isNaN(temp) ? isAlgorithms(rule[rule.length - 1]) : isAlgorithms(rule[rule.length - 2]);
|
152 | })();
|
153 |
|
154 | //如果配置中有声明图像处理引擎,则传入引擎到配置中
|
155 | if(fws.config.imgEngine !== '' && typeof fws.config.imgEngine === 'string'){
|
156 | option.engine = require(fws.config.imgEngine);
|
157 | };
|
158 |
|
159 | //生成png精灵图、sass文件、以及更新项目精灵图索引(sass)
|
160 | let pngMotor = (err,result)=>{
|
161 | if(err){
|
162 | reject({
|
163 | status:'error',
|
164 | msg:`编译出错 ${config.srcDir} *.png`,
|
165 | info:err
|
166 | });
|
167 | }else{
|
168 | //精灵图数据
|
169 | let spriteData = {
|
170 | size:{},
|
171 | spriteNames:[],
|
172 | element:{},
|
173 | url:'',
|
174 | path:''
|
175 | },
|
176 |
|
177 | //精灵图目录名称
|
178 | dirName = (()=>{
|
179 | let pathDirs = config.srcDir.split(m.path.sep);
|
180 | return pathDirs[pathDirs.length-1];
|
181 | })(),
|
182 |
|
183 | //sass文件输出路径
|
184 | spriteSassDistPath = m.path.join(config.distScssDir,'_spriteData',dirName+'.scss'),
|
185 |
|
186 | //sass文件输出目录
|
187 | spriteSassDistDir = m.path.dirname(spriteSassDistPath),
|
188 |
|
189 | //精灵图输出路径
|
190 | spriteDist = m.path.join(config.distSpreiteDir,srcDirName+'.png');
|
191 |
|
192 | let outTask = [];
|
193 |
|
194 | //保存精灵图图片
|
195 | outTask.push(()=>{
|
196 | return new Promise((resolve,reject)=>{
|
197 | if(result.image){
|
198 | m.fs.ensureDir(config.distSpreiteDir,err => {
|
199 | if(err){
|
200 | resolve({
|
201 | status:'error',
|
202 | msg:`创建失败 ${config.distSpreiteDir}`,
|
203 | info:err
|
204 | });
|
205 | }else{
|
206 | try {
|
207 | m.fs.writeFileSync(spriteDist,result.image);
|
208 | resolve({
|
209 | status:'success',
|
210 | msg:`生成 ${spriteDist}`,
|
211 | distPath:spriteDist,
|
212 | data:result.image
|
213 | });
|
214 | } catch (err) {
|
215 | reject({
|
216 | status:'error',
|
217 | msg:`写入失败 ${spriteDist}`,
|
218 | distPath:spriteDist,
|
219 | info:err
|
220 | });
|
221 | };
|
222 | }
|
223 | });
|
224 | };
|
225 | });
|
226 | });
|
227 |
|
228 | //保存精灵图SASS
|
229 | outTask.push(()=>{
|
230 | return new Promise((resolve,reject)=>{
|
231 | //得到精灵图的url
|
232 | spriteData.url = (()=>{
|
233 | let sPath = '';
|
234 |
|
235 | //如果不是在fws环境下使用,图片url为文件名,否则根据fws.devPath的目录来生成相对的
|
236 | if(global.fws){
|
237 | let surl = spriteDist.replace(fws.devPath,'');
|
238 | //sPath = '..';
|
239 | surl = surl.split(_ts.m.path.sep);
|
240 | surl.forEach((item,index)=>{
|
241 | if(item !== ''){
|
242 | let sep = !sPath ? '' : '/';
|
243 | sPath += sep+item;
|
244 | };
|
245 | });
|
246 | }else{
|
247 | sPath = dirName+'.png';
|
248 | };
|
249 | return sPath;
|
250 | })();
|
251 |
|
252 | //得到精灵图输出路径
|
253 | spriteData.path = spriteDist;
|
254 |
|
255 | //精灵图元素数据
|
256 | if(result.coordinates){
|
257 | for(let i in result.coordinates){
|
258 | let fileType = _ts.m.path.extname(i),
|
259 | fileName = _ts.m.path.basename(i,fileType);
|
260 | spriteData.spriteNames.push(fileName);
|
261 | spriteData.element[fileName] = result.coordinates[i];
|
262 | };
|
263 | };
|
264 | spriteData.spriteNames = '_!@!&_'+spriteData.spriteNames.toString()+'_&!@!_';
|
265 |
|
266 | //精灵图大小
|
267 | if(result.properties){
|
268 | spriteData.size.width = result.properties.width;
|
269 | spriteData.size.height = result.properties.height;
|
270 | };
|
271 |
|
272 | //将对象数据转为sass字符串
|
273 | let sSpriteData = JSON.stringify(spriteData,null,2);
|
274 |
|
275 | sSpriteData = sSpriteData.replace('"_!@!&_','(');
|
276 | sSpriteData = sSpriteData.replace('_&!@!_"',')');
|
277 | sSpriteData = sSpriteData.replace(/{/g,'(');
|
278 | sSpriteData = sSpriteData.replace(/}/g,')');
|
279 |
|
280 | sSpriteData = '$'+dirName+': '+sSpriteData;
|
281 |
|
282 | //保存sass 文件
|
283 | m.fs.ensureDir(spriteSassDistDir,err => {
|
284 | if(err){
|
285 | reject({
|
286 | status:'error',
|
287 | msg:`创建失败 ${spriteSassDistDir}`,
|
288 | info:err
|
289 | });
|
290 | }else{
|
291 | try {
|
292 | m.fs.writeFileSync(spriteSassDistPath,sSpriteData);
|
293 | resolve({
|
294 | status:'success',
|
295 | msg:`写入 ${spriteSassDistPath}`,
|
296 | distPath:spriteSassDistPath,
|
297 | data:sSpriteData
|
298 | });
|
299 | } catch (error) {
|
300 | reject({
|
301 | status:'error',
|
302 | msg:`写入失败 ${spriteSassDistPath}`,
|
303 | distPath:spriteSassDistPath,
|
304 | info:err
|
305 | });
|
306 | };
|
307 | };
|
308 | });
|
309 | });
|
310 | });
|
311 |
|
312 | //更新精灵图数据(scss/_spriteData.scss)到项目
|
313 | outTask.push(()=>{
|
314 | return new Promise((resolve,reject)=>{
|
315 | let aSdFileList = m.fs.readdirSync(spriteSassDistDir),
|
316 | aSdList = [],
|
317 | _spriteDataContent = '@charset "utf-8";\r\n//以下内容由程序自动更新\r\n',
|
318 | re = /^(\w*).(scss)$/i;
|
319 | //筛选出非标准的.scss文件
|
320 | aSdFileList.forEach((item,index)=>{
|
321 | if(re.test(item)){
|
322 | _spriteDataContent += '@import "./_spriteData/'+item+'";\r\n';
|
323 | aSdList.push(item);
|
324 | };
|
325 | });
|
326 |
|
327 | //更新
|
328 | let _fws_spriteDataPath = m.path.join(config.distScssDir,'_spriteData.scss');
|
329 |
|
330 | m.fs.ensureDir(config.distScssDir,err => {
|
331 | if(err){
|
332 | reject({
|
333 | status:'error',
|
334 | msg:`创建失败 ${config.distScssDir}`,
|
335 | info:err
|
336 | });
|
337 | }else{
|
338 | try {
|
339 | m.fs.writeFileSync(_fws_spriteDataPath,_spriteDataContent);
|
340 | resolve({
|
341 | status:'success',
|
342 | msg:`写入 ${_fws_spriteDataPath}`,
|
343 | distPath:_fws_spriteDataPath,
|
344 | data:_spriteDataContent
|
345 | });
|
346 | } catch (error) {
|
347 | reject({
|
348 | status:'error',
|
349 | msg:`写入失败 ${_fws_spriteDataPath}`,
|
350 | distPath:_fws_spriteDataPath,
|
351 | info:error
|
352 | });
|
353 | };
|
354 | };
|
355 | });
|
356 |
|
357 | });
|
358 | });
|
359 |
|
360 | let pngTaskAsync = async ()=>{
|
361 | let data = {};
|
362 | for(let i=0,len = outTask.length; i<len; i++){
|
363 | let result = await outTask[i]();
|
364 |
|
365 | switch (i) {
|
366 | case 0:
|
367 | data['spritePng'] = result.data;
|
368 | break;
|
369 |
|
370 | case 1:
|
371 | data['spriteScss'] = result.data;
|
372 | break;
|
373 |
|
374 | case 2:
|
375 | data['spriteScssMap'] = result.data;
|
376 | break;
|
377 | };
|
378 | };
|
379 | return {
|
380 | status:'success',
|
381 | msg:`PNG精灵 ${config.srcDir}`,
|
382 | data:data
|
383 | };
|
384 | };
|
385 | pngTaskAsync().then(v => {
|
386 | resolve(v);
|
387 | }).catch(e => {
|
388 | reject(e);
|
389 | });
|
390 |
|
391 | };
|
392 | };
|
393 |
|
394 | m.spritesmith.run(option,pngMotor);
|
395 | }else{
|
396 | resolve({
|
397 | status:'success',
|
398 | msg:'目录无Png图片要处理'
|
399 | });
|
400 | };
|
401 | }));
|
402 |
|
403 | return Promise.all(spriteTaskList);
|
404 | }
|
405 |
|
406 | //获取图片列表
|
407 | getImgList(){
|
408 | const _ts = this,
|
409 | m = _ts.m,
|
410 | config = _ts.config;
|
411 |
|
412 | let imgList = {
|
413 | 'png':[],
|
414 | 'svg':[]
|
415 | },
|
416 | files = m.fs.readdirSync(config.srcDir);
|
417 | files.forEach((item,index)=>{
|
418 | let type = item.substr(item.length - 4,4).toLowerCase(),
|
419 | filePath = m.path.join(config.srcDir,item);
|
420 | switch (type) {
|
421 | case '.png':
|
422 | imgList.png.push(filePath);
|
423 | break;
|
424 |
|
425 | case '.svg':
|
426 | imgList.svg.push(filePath);
|
427 | break;
|
428 | };
|
429 | });
|
430 | return imgList;
|
431 | }
|
432 |
|
433 | };
|
434 |
|
435 | module.exports = OutSprite;
|