grunt-sprite
Version:
A grunt plugin to help front engineer creating css sprite.
283 lines (217 loc) • 11.5 kB
JavaScript
var spritesmith = require('spritesmith'),
fs = require('fs'),
path = require('path'),
gm = require('gm');
module.exports = function (grunt) {
"use strict";
// Export the SpriteMaker function
grunt.registerMultiTask('sprite', 'Create sprite image with slices and update the CSS file.', function () {
var that = this,
done = this.async(),
log = console.log;
var options = this.options({
'imagestamp':false,
'cssstamp':false,
'newsprite':false
});
var hasDelete = false;
var _spriteSmithWrapper = function (file, callback) {
var src = file.src[0],
destDir = file.orig.dest,
dest = file.dest,
timeNow = grunt.template.today("yyyymmddHHmmss"),
regex = new RegExp('\\/?([^.\\/]*)\\..*', 'ig'),
fileNameList = src.match(regex),
fileName = fileNameList[0].replace(regex, '$1'),
destCSS = destDir + "css/" + fileName + file.orig.ext,
destSPRITE = destDir + "sprite/" + fileName + ".png";
if (options.newsprite) {
fileName = fileName + "-" + timeNow;
destSPRITE = destDir + "sprite/" + fileName + ".png";
}
var cssSrc=path.dirname(src);
var rootPath = path.dirname(cssSrc).toString();
var sliceList = [], //
sliceCodeList = [], //background-image:url("../slice/xxx.png")
sliceImgUrlList = [], //slice/xxx.png
sliceCode = [], //
data = []; //
// 获取slice部分的代码
var getSliceList = function () {
var regex = new RegExp('background-image:[\\s]*url\\(["\']?(?!http[s]?|/)[^;]*?(slice[\\w\\d\\s!./\\-\\_@]*\\.[\\w?#]+)["\']?\\)[^;}]*;?', 'ig'),
pathToResource;
// 获取整个样式
data = grunt.file.read(src);
// 匹配到所有的background-image:url("../slice/xxx.png")
sliceCodeList = data.match(regex);
// 对background-image进行遍历,取出其中的slice/xxx.png
if (sliceCodeList !== null) {
for (var x = 0; x < sliceCodeList.length; x++) {
// 匹配到css里图片的路径信息 slice/xxx.png
pathToResource = sliceCodeList[x].replace(regex, '$1');
sliceImgUrlList[x] = pathToResource;
sliceList[pathToResource] = sliceCodeList[x];
}
}
else callback(false);
};
// 生成雪碧图
var createSprite = function () {
var config = that.data.options;
var imglist = sliceImgUrlList;
if(rootPath !=".") for(var i =0 ; i< imglist.length ;i++)
{
imglist[i] = rootPath + '/' + imglist[i];
}
config.src = imglist;
if (sliceImgUrlList.length > 0) {
// spritesmiteh 库调用
spritesmith(config, function (err, result) {
if (err) {
grunt.fatal(err);
return callback(err);
}
else {
// 生成sprite
grunt.file.write(destSPRITE, result.image, { encoding: 'binary' });
grunt.log.writelns(("Done! [Created] -> " + destSPRITE));
var tmpResult = result.coordinates;
for (var key in tmpResult) {
var newKey = key;
if(rootPath != ".") newKey = key.slice(rootPath.length + 1, key.length);
sliceCode[ newKey ] = tmpResult[ key ];
sliceCode[ newKey ].sprite = sliceList[ newKey ];
}
replaceCode();
}
});
}
else {
grunt.file.copy(src, destCSS);
grunt.log.writelns(("Done! [Copied] -> " + destCSS));
}
};
// 替换css里的background-image代码部分
var replaceCode = function () {
var img;
var datasprite = grunt.file.read(src);
for (var key in sliceCode) {
img = sliceCode[ key ];
var code ='background-image: url("../sprite/' + fileName + '.png';
if(options.imagestamp) code+="?"+timeNow;
code+= '");';
code+=' background-position: -' + img.x + 'px -' + img.y + 'px;';
datasprite = datasprite.replace(img.sprite,code);
}
// 生成新的样式
grunt.file.write(destCSS, datasprite);
grunt.log.writelns(("Done! [Created] -> " + destCSS));
retinaSprite();
};
// 生成retina雪碧图
var retinaSprite = function () {
var regex = new RegExp('.*(slice\\/[\\w\\d\\s!./\\-\\_@]*)\\.([\\w?#]+)["\']?\\)[^;}]*;?', 'ig');
// var regex = new RegExp('.*slice\\/(.*)\\.(\\w*)', 'ig');
RegExp.escape = function (s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};
var retinaSliceList = [];
var retinaImgUrlList = [];
var retinaSpriteSize = new Object();
for (var i = 0; i < sliceCodeList.length; i++) {
var key = sliceCodeList[i];
var retinaIconUrl = key.replace(regex, '$1') + "@2x." + key.replace(regex, '$2');
if(rootPath !=".") retinaIconUrl = rootPath + '/' + retinaIconUrl;
if (grunt.file.exists(retinaIconUrl)) {
var regexClassNameString = '(\\.?[^}]*?)\\s?{\\s?[^}]*?' + RegExp.escape(key);
var regexClassName = new RegExp(regexClassNameString, 'ig');
var classNameResult = data.match(regexClassName);
var className = classNameResult[0].replace(regexClassName, '$1');
var retinaItem = new Object();
retinaImgUrlList.push(retinaIconUrl);
retinaItem.className = className;
retinaSliceList[retinaIconUrl] = retinaItem;
}
}
if (retinaImgUrlList.length > 0) {
var config = that.data.options;
var spritedest = destDir +"sprite/" + fileName + "@2x.png";
config.src = retinaImgUrlList;
// spritesmiteh 库调用
spritesmith(config, function (err, result) {
if (err) {
grunt.fatal(err);
return callback(err);
}
else {
// 生成sprite
grunt.file.write(spritedest, result.image, { encoding: 'binary' });
gm(spritedest).size(function (err, size) {
if (!err) {
retinaSpriteSize = size;
if (retinaSpriteSize.width % 2 == 1 || retinaSpriteSize.height % 2 == 1) {
grunt.fail.fatal("警告:所有的雪碧图icon尺寸必须是偶数的!请检查!");
}
grunt.log.writelns(("Done! [Created] -> " + spritedest));
var tmpResult = result.coordinates;
// console.log(tmpResult);
for (var key in tmpResult) {
retinaSliceList[ key ].sprite = tmpResult[ key ];
}
createRetinaCode(retinaSliceList, retinaSpriteSize);
addCssStamp();
callback(false);
}
});
}
});
}
else callback(false);
};
var addCssStamp = function(){
var sourcedataurl = destCSS;
var sourcedata = grunt.file.read(sourcedataurl);
if(options.cssstamp) sourcedata+='.TmTStamp{content:"'+ timeNow +'"}';
grunt.file.write(destCSS, sourcedata);
};
var createRetinaCode = function (retinaSliceCode, retinaSpriteSize) {
var sourcedataurl = destCSS;
var sourcedata = grunt.file.read(sourcedataurl);
var retinaCssCode = '';
var img;
retinaCssCode += '\n\n@media only screen and (-webkit-min-device-pixel-ratio: 1.5),only screen and (min--moz-device-pixel-ratio: 1.5),only screen and (min-resolution: 240dpi) \n{';
for (var key in retinaSliceCode) {
img = retinaSliceCode[key].sprite;
retinaCssCode += retinaSliceCode[key].className;
retinaCssCode += '{background-image:url("../sprite/' + fileName + '@2x.png';
if(options.imagestamp) retinaCssCode+="?"+timeNow;
retinaCssCode +='");';
retinaCssCode += 'background-position: -' + (img.x) / 2 + 'px -' + (img.y) / 2 + 'px;background-size:' + (retinaSpriteSize.width) / 2 + 'px;}';
}
retinaCssCode += '\n}\n';
sourcedata += retinaCssCode;
grunt.file.write(destCSS, sourcedata);
grunt.log.writelns(("Done! [Retina 2x] -> " + destCSS));
};
getSliceList();
createSprite();
};
// Process starter
// 删掉之前生成的雪碧图
for(var key in this.files){
var file = this.files[key];
//log(file);
//log('file src'+file.src);
var spritePath = path.join(path.dirname(file.src),'..','sprite');
log('sprite path : ----- '+spritePath);
//if the sprite dir path is exists
if(grunt.file.exists(spritePath)) {
grunt.file.delete(spritePath);
}
break;
}
grunt.util.async.forEachSeries(this.files, _spriteSmithWrapper, function (err) {
done();
});
});
};