├── .gitignore
├── README.md
├── dist
├── example.html
├── example.jpg
└── onepx.js
├── gulpfile.js
├── package.json
└── src
├── example.html
├── example.jpg
└── onepx.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ##onepx
2 |
3 | onepx is a javascript function to draw a retina 1px line without changing any css.
4 |
5 | ##How to use
6 |
7 | ```html
8 |
9 |
10 |
13 | ```
14 |
15 | ##API
16 |
17 | ###onepx(selectors, [parentSelector][isListen]);
18 |
19 | - `selectors`:String, the selectors that need to be drew the retina 1px line, separated by commas.
20 |
21 | - `parentSelector`:String, targets's parent selector, used to reduce the scope of observing, default `body`
22 |
23 | - `isListen`:Boolean, observe the element if true, when element added dynamically, check them if need to drew 1px
24 |
25 |
26 |
27 | ###More Examples
28 | Elements match ".item" will be drew
29 | ```script
30 | onepx(".item");
31 | ```
32 |
33 | Elements match ".item" or ".box" will be drew
34 | ```script
35 | onepx(".item, .box");
36 | ```
37 |
38 | Elements match ".item" which parents match ".list" will be drew
39 | ```script
40 | onepx(".item", ".list");
41 | ```
42 |
43 | Elements match ".appendItem" will be drew, even they are added dynamically
44 | ```script
45 | onepx(".appendItem", true);
46 | ```
47 |
48 | #### If you want to custom style to 1px line, use like this
49 | ```html
50 |
51 | ```
52 |
53 | The rule is: selector@custom style&selector@custom style&...
54 |
55 | #### As onepx draw 1px line after the onepx.js loaded, it may cause page flicker.If you mind, you can do like this:
56 | ```html
57 |
58 | ...
59 |
63 |
64 | ```
65 |
66 | ##DEMO
67 |
68 | 
69 |
70 | ##License
71 |
72 | onepx is available under the terms of the [MIT License](http://www.opensource.org/licenses/mit-license.php).
73 |
--------------------------------------------------------------------------------
/dist/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | onepx
7 |
20 |
21 |
22 |
23 |
24 |
Normal Element
25 |
29 |
30 |
31 |
Border Radius
32 |
33 |
34 |
35 |
36 |
Replaced Element
37 |

38 |

39 |
40 |
41 |
List
42 |
43 | - 1
44 | - 2
45 | - 3
46 | - 4
47 | - 5
48 |
49 |
50 |
51 |
Click Event & Active
52 |
60 |
61 |
Add Dynamically
62 |
63 |
64 | Add an onepx box
65 |
66 |
67 |
82 |
83 |
--------------------------------------------------------------------------------
/dist/example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechatui/onepx/73b942379a04386c59a9a0423e1f6c0efb7ed4eb/dist/example.jpg
--------------------------------------------------------------------------------
/dist/onepx.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"use strict";"function"==typeof define?define(t):window[e]=t()}("onepx",function(){function e(e){l.push(e),setTimeout(function(){for(var e="";l.length;)e+="\n"+l.shift();if(e){var t=document.createElement("style");t.type="text/css",t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.createTextNode(e)),document.head.appendChild(t)}},100)}function t(e,t){var n=window.MutationObserver||window.WebKitMutationObserver;n?new n(function(e){for(var n=0,o=e.length;o>n;++n){var r=e[n],i=r.addedNodes;if(i&&i.length)for(var a=0,d=i.length;d>a;++a){var s=i[a];t(s)}}}).observe(e,{childList:!0,subtree:!0}):window.addEventListener&&e.addEventListener("DOMNodeInserted",function(e){t(e.target)},!1)}function n(e,t){var n="";return window.getComputedStyle?n=window.getComputedStyle(e,"").getPropertyValue(t):document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(e,"").getPropertyValue(t):e.currentStyle&&(t=t.replace(/\-(\w)/g,function(e,t){return t.toUpperCase()}),n=e.currentStyle[t]),n}function o(e){for(var t,n="",o=0,r=c.length;r>o;++o){var i=c[o];if(t=e(i),"continue"!=t){if("break"==t)break;n+=t}}return n}function r(e){for(var t="",o=0,r=p.length;r>o;++o){var i=p[o],a=n(e,"border-"+i+"-radius"),d="";"0px"!=a&&(d=";border-"+i+"-radius:",a.indexOf("%")>-1?d+=a:(a=a.match(/([-\d\.]*)(\w*)/),d+=""+2*a[1]+a[2])),t+=d}return t+";"}function i(e){return o(function(t){var o=n(e,"border-"+t+"-width");return!o||o.indexOf("1px")<0?"continue":";border-"+t+": "+o+" "+n(e,"border-"+t+"-style")+" "+n(e,"border-"+t+"-color")})}function a(t){var a=i(t);if(a){a+=r(t);var d=document.createElement("span");d.className="onepxHelper",d.id="onepx"+ ++u,a="#"+d.id+"{"+a+"}";var s=t.getAttribute("onepx");if(s)for(var l=s.split("&"),f=0,c=l.length;c>f;++f){var p=l[f].split("@");if(p.length<2)return;var v=p[0],b=p[1];a=a+"\n"+v+" #"+d.id+"{"+b+";}"}e(a);var m=t.nodeName;if(t.style.border="0","IMG"==m||"INPUT"==m||"TEXTAREA"==m||"SELECT"==m||"OBJECT"==m){var g="",w=n(t,"position"),y=n(t,"display"),h="";g=o(function(e){var o=n(t,"margin-"+e);return o?";margin-"+e+": "+o:"continue"}),"inline"==y&&(y="inline-block"),"absolute"!=w&&"relative"!=w?w="relative":h=o(function(e){var o=n(t,e);return o&&"auto"!=o?";"+e+": "+o:"continue"}),t.style.fontSize=n(t,"font-size"),t.style.margin="0",t.style.position="static",t.outerHTML=""+d.outerHTML+t.outerHTML+""}else{var x=n(t,"position");"absolute"!=x&&"relative"!=x&&"fixed"!=x&&(t.style.position="relative"),t.appendChild(d),t.setAttribute("onepxset","")}}}function d(e,t){for(var n=0,o=t.length;o>n;++n)for(var r=e.querySelectorAll(t[n]+":not(.onepxHelper):not([onepxset])"),i=0,d=r.length;d>i;++i)a(r[i])}function s(o,r,i){var a=window.devicePixelRatio||window.screen.deviceXDPI/window.screen.logicalXDPI||1;if(!(1>=a)&&n(document.body,"display")&&o){f||(e("/*Use to draw 1px line.*/\n.onepxHelper{ position:absolute;pointer-events:none;top:0;left:0;width:200%;height:200%;-webkit-transform-origin: 0 0;-ms-transform-origin: 0 0;transform-origin: 0 0;-webkit-transform:scale(0.5);-ms-transform:scale(0.5);transform:scale(0.5);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box }"),f=!0),"boolean"==typeof r&&(i=r,r=void 0);var s=r&&document.querySelector(r)||document.body,l=o.split(",");d(s,l),i&&t(s,function(e){if(1==e.nodeType){var t=e.parentNode;t&&"HTML"!=t.tarName||(t=document.body),d(t,l)}})}}var l=[],u=0,f=!1,c=["top","right","bottom","left"],p=["top-left","top-right","bottom-right","bottom-left"];return s});
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by BearJ on 2015/4/1.
3 | */
4 |
5 | var gulp = require("gulp");
6 | var uglify = require("gulp-uglify");
7 |
8 | gulp.task("default", function () {
9 | gulp.src("src/onepx.js")
10 | .pipe(uglify())
11 | .pipe(gulp.dest("dist"));
12 |
13 | gulp.src("src/**/!(onepx.js)")
14 | .pipe(gulp.dest("dist"));
15 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "onepx",
3 | "description": "onepx.js",
4 | "author": "wechat ui team",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/wechatui/onepx.git"
8 | },
9 | "license": "MIT",
10 | "devDependencies": {
11 | "gulp": "~3.8.10",
12 | "gulp-uglify": "^1.0.2"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | onepx
7 |
20 |
21 |
22 |
23 |
24 |
Normal Element
25 |
29 |
30 |
31 |
Border Radius
32 |
33 |
34 |
35 |
36 |
Replaced Element
37 |

38 |

39 |
40 |
41 |
List
42 |
43 | - 1
44 | - 2
45 | - 3
46 | - 4
47 | - 5
48 |
49 |
50 |
51 |
Click Event & Active
52 |
60 |
61 |
Add Dynamically
62 |
63 |
64 | Add an onepx box
65 |
66 |
67 |
82 |
83 |
--------------------------------------------------------------------------------
/src/example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechatui/onepx/73b942379a04386c59a9a0423e1f6c0efb7ed4eb/src/example.jpg
--------------------------------------------------------------------------------
/src/onepx.js:
--------------------------------------------------------------------------------
1 | /**
2 | * onepx.js
3 | * Author: BearJ
4 | * Version: v1.5.0
5 | * Date: 2015-4-1
6 | */
7 | (function (name, definition) {
8 | "use strict";
9 |
10 | if (typeof define === "function") {
11 | define(definition);
12 | } else {
13 | window[name] = definition();
14 | }
15 | })("onepx", function () {
16 | var _addStyleCache = [], // Style to be added
17 | _helperID = 0, // ID for each helper
18 | _hasSetHelperCommonStyle = false, // If helper common style had set
19 | _directions = ["top", "right", "bottom", "left"], // Used for calculating style which has these directions
20 | _tDirections = ["top-left", "top-right", "bottom-right", "bottom-left"] // Used for calculating border style from these directions
21 | ;
22 |
23 |
24 | /**
25 | * Add style to
26 | * @param cssText
27 | * @private
28 | */
29 | function _addStyle2Head(cssText) {
30 | _addStyleCache.push(cssText);
31 | setTimeout(function () {
32 | var stylesText = "";
33 | while (_addStyleCache.length) {
34 | stylesText += "\n" + _addStyleCache.shift();
35 | }
36 |
37 | if (!stylesText) return;
38 |
39 | var style = document.createElement("style");
40 | style.type = "text/css";
41 | if (style.styleSheet) {
42 | style.styleSheet.cssText = stylesText;
43 | } else {
44 | style.appendChild(document.createTextNode(stylesText));
45 | }
46 | document.head.appendChild(style);
47 | }, 100);
48 | }
49 |
50 | /**
51 | * Observe dom
52 | * @param dom need to be ovserved
53 | * @param callback call when dom has changed
54 | * @private
55 | */
56 | function _observeDOMInsert(dom, callback) {
57 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
58 | if (MutationObserver) {
59 | new MutationObserver(function (mutations) {
60 | for (var i = 0, lenI = mutations.length; i < lenI; ++i) {
61 | var mutation = mutations[i], addedNodes = mutation.addedNodes;// 有增加元素
62 | if (addedNodes && addedNodes.length) {
63 | for (var j = 0, lenJ = addedNodes.length; j < lenJ; ++j) {
64 | var node = addedNodes[j];
65 | callback(node);
66 | }
67 | }
68 | }
69 | }).observe(dom, {childList: true, subtree: true});
70 | }
71 | else if (window.addEventListener) {
72 | dom.addEventListener("DOMNodeInserted", function (e) {
73 | callback(e.target);
74 | }, false);
75 | }
76 | }
77 |
78 | /**
79 | * Get style value
80 | * @param ele element
81 | * @param rule css rule
82 | * @return {string} result
83 | * @private
84 | */
85 | function _getStyleVal(ele, rule) {
86 | var strValue = "";
87 | if (window.getComputedStyle) {
88 | strValue = window.getComputedStyle(ele, "").getPropertyValue(rule);
89 | }
90 | else if (document.defaultView && document.defaultView.getComputedStyle) {
91 | strValue = document.defaultView.getComputedStyle(ele, "").getPropertyValue(rule);
92 | }
93 | else if (ele.currentStyle) {
94 | rule = rule.replace(/\-(\w)/g, function (strMatch, p1) {
95 | return p1.toUpperCase();
96 | });
97 | strValue = ele.currentStyle[rule];
98 | }
99 | return strValue;
100 | }
101 |
102 | /**
103 | * Generate style names with directions
104 | * @param callback pass direction as arguments. e.g.: callback("top") -> "margin-top"
105 | * @return {string}
106 | * @private
107 | */
108 | function _genStyleNameWithDirection(callback) {
109 | var out = "", result;
110 | for (var i = 0, len = _directions.length; i < len; ++i) {
111 | var dir = _directions[i];
112 | result = callback(dir);
113 | if (result == "continue") continue;
114 | else if (result == "break") break;
115 |
116 | out += result;
117 | }
118 | return out;
119 | }
120 |
121 | /**
122 | * Calculate border-radius style value. Use direction for IE compatibility
123 | * @param ele elemetn
124 | * @return {string} e.g.: border-top-radius:1px; border-right-radius:1px;...
125 | * @private
126 | */
127 | function _calcBorderRadiusStyle(ele) {
128 | var out = "";
129 | for (var i = 0, lenI = _tDirections.length; i < lenI; ++i) {
130 | var dir = _tDirections[i],
131 | br = _getStyleVal(ele, "border-" + dir + "-radius"), val = "";
132 |
133 | if (br != "0px") {
134 | val = ";" + "border-" + dir + "-radius:";
135 | if (br.indexOf("%") > -1) val += br;
136 | else {
137 | br = br.match((/([-\d\.]*)(\w*)/));
138 | val += "" + br[1] * 2 + br[2]
139 | }
140 | }
141 | out += val;
142 | }
143 | return out + ";";
144 | }
145 |
146 | /**
147 | * Calcute border style value(except border-radius).
148 | * @param ele
149 | * @return {string}
150 | * @private
151 | */
152 | function _calcBorderNormalStyle(ele) {
153 | return _genStyleNameWithDirection(function (dir) {
154 | var val = _getStyleVal(ele, "border-" + dir + "-width");
155 | if (!val || val.indexOf("1px") < 0) return "continue";
156 |
157 | return ";border-" + dir + ": " + val + " " + _getStyleVal(ele, "border-" + dir + "-style") + " " + _getStyleVal(ele, "border-" + dir + "-color");
158 | });
159 | }
160 |
161 | /**
162 | * Set helper to realize retina 1px line
163 | * @param ele
164 | * @private
165 | */
166 | function _setHelper(ele) {
167 | var helperStyle = _calcBorderNormalStyle(ele);
168 | if (!helperStyle) return;
169 |
170 | helperStyle += _calcBorderRadiusStyle(ele);
171 |
172 | // Create helper
173 | var helper = document.createElement("span");
174 | helper.className = "onepxHelper";
175 | helper.id = "onepx" + ++_helperID;
176 | helperStyle = "#" + helper.id + "{" + helperStyle + "}";
177 |
178 | // Add custom style to helper
179 | var customStyle = ele.getAttribute("onepx");
180 | if (customStyle) {
181 | var mods = customStyle.split("&");
182 | for (var i = 0, len = mods.length; i < len; ++i) {
183 | var mod = mods[i].split("@");
184 | if (mod.length < 2) return;
185 |
186 | var parents = mod[0], css = mod[1];
187 | helperStyle = helperStyle + "\n" + parents + " #" + helper.id + "{" + css + ";}"
188 | }
189 | }
190 | _addStyle2Head(helperStyle);
191 |
192 | // If the element is inline-element, wrap a help to realize
193 | var nodeName = ele.nodeName;
194 | ele.style.border = "0";
195 | if (nodeName == "IMG" || nodeName == "INPUT" || nodeName == "TEXTAREA" || nodeName == "SELECT" || nodeName == "OBJECT") {
196 | var margin = "", position = _getStyleVal(ele, "position"), display = _getStyleVal(ele, "display"), offset = "";
197 | margin = _genStyleNameWithDirection(function (dir) {
198 | var val = _getStyleVal(ele, "margin-" + dir);
199 | if (!val) return "continue";
200 | return ";margin-" + dir + ": " + val;
201 | });
202 | if (display == "inline") {
203 | display = "inline-block";
204 | }
205 | if (position != "absolute" && position != "relative") {
206 | position = "relative";
207 | } else {
208 | offset = _genStyleNameWithDirection(function (dir) {
209 | var val = _getStyleVal(ele, dir);
210 | if (!val || val == "auto") return "continue";
211 | return ";" + dir + ": " + val;
212 | });
213 | }
214 | ele.style.fontSize = _getStyleVal(ele, "font-size");
215 | ele.style.margin = "0";
216 | ele.style.position = "static";
217 | ele.outerHTML =
218 | ""
219 | + helper.outerHTML
220 | + ele.outerHTML
221 | + ""
222 | } else {
223 | var elePos = _getStyleVal(ele, "position");
224 | if (elePos != "absolute" && elePos != "relative" && elePos != "fixed") {
225 | ele.style.position = "relative";
226 | }
227 | ele.appendChild(helper);
228 | ele.setAttribute("onepxset", "");
229 | }
230 | }
231 |
232 | /**
233 | * Set helpers to element which match the selectors
234 | * @param ele
235 | * @param selectors
236 | * @private
237 | */
238 | function _setHelpers(ele, selectors) {
239 | for (var i = 0, lenI = selectors.length; i < lenI; ++i) {
240 | var eles = ele.querySelectorAll(selectors[i] + ":not(.onepxHelper):not([onepxset])");
241 | for (var j = 0, lenJ = eles.length; j < lenJ; ++j) {
242 | _setHelper(eles[j]);
243 | }
244 | }
245 | }
246 |
247 |
248 | /**
249 | * onepx
250 | * @param targetSelectors the selectors that need to be drew the retina 1px line
251 | * @param parentSelector targets's parent selector, used to reduce the scope of observing, default "body"
252 | * @param isListen observe the element if true, when element added dynamically, check them if need to drew 1px
253 | * @private
254 | */
255 | function onepx(targetSelectors, parentSelector, isListen) {
256 | var devicePixRatio = window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI) || 1;
257 | if (devicePixRatio <= 1 || !_getStyleVal(document.body, "display") || !targetSelectors) return;
258 |
259 | // Add common style for helper to
260 | if (!_hasSetHelperCommonStyle) {
261 | _addStyle2Head(
262 | "/*Use to draw 1px line.*/\n" +
263 | ".onepxHelper{ position:absolute;pointer-events:none;top:0;left:0;width:200%;height:200%;"
264 | + "-webkit-transform-origin: 0 0;-ms-transform-origin: 0 0;transform-origin: 0 0;"
265 | + "-webkit-transform:scale(0.5);-ms-transform:scale(0.5);transform:scale(0.5);"
266 | + "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box"
267 | + " }"
268 | );
269 | _hasSetHelperCommonStyle = true;
270 | }
271 |
272 | // If the second argument was boolean, use as isListen
273 | if (typeof(parentSelector) == "boolean") {
274 | isListen = parentSelector;
275 | parentSelector = undefined;
276 | }
277 |
278 | // Get selectors which will be drew retina 1px line
279 | var parent = (parentSelector && document.querySelector(parentSelector)) || document.body,
280 | tgtSelectors = targetSelectors.split(",");
281 | _setHelpers(parent, tgtSelectors);
282 |
283 | // If isListen was true, observe the element
284 | if (isListen) {
285 | _observeDOMInsert(parent, function (ele) {
286 | if (ele.nodeType != 1) return;
287 |
288 | var eleParent = ele.parentNode;
289 | if (!eleParent || eleParent.tarName == "HTML") eleParent = document.body;
290 | _setHelpers(eleParent, tgtSelectors);
291 | });
292 | }
293 | }
294 |
295 | return onepx;
296 | });
--------------------------------------------------------------------------------