├── .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 | ![](http://wechatui.github.io/onepx/dist/qrcode.png) 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 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |

    Border Radius

    32 |
    33 |
    34 |
    35 |
    36 |

    Replaced Element

    37 | 38 | 39 |
    40 |
    41 |

    List

    42 | 49 |
    50 |
    51 |

    Click Event & Active

    52 |
    53 |
    54 | no onepx 55 |
    56 |
    57 | has onepx 58 |
    59 |
    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 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |

    Border Radius

    32 |
    33 |
    34 |
    35 |
    36 |

    Replaced Element

    37 | 38 | 39 |
    40 |
    41 |

    List

    42 | 49 |
    50 |
    51 |

    Click Event & Active

    52 |
    53 |
    54 | no onepx 55 |
    56 |
    57 | has onepx 58 |
    59 |
    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 | }); --------------------------------------------------------------------------------