├── .gitattributes ├── .gitignore ├── README.md ├── dist ├── crop.html ├── crop.min.js └── crop.js ├── package.json ├── LICENSE └── src └── crop.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw* 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 无依赖的图片裁剪库 2 | ![demo.gif](https://i.loli.net/2019/08/21/rapFDu5UVs2By3M.gif) 3 | 4 | ## 安装 5 | 6 | ```sh 7 | npm install --save @zee.kim/crop 8 | ``` 9 | 10 | ## 使用 11 | 12 | ```javascript 13 | new crop({ 14 | width: 300, 15 | height: 300, 16 | //url:"", //默认显示图片 17 | success: function (data) { 18 | console.log(data);//这是个base64 图片字符串 19 | } 20 | }); 21 | ``` 22 | -------------------------------------------------------------------------------- /dist/crop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | crop 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": false, 3 | "name": "@zee.kim/crop", 4 | "version": "1.0.0", 5 | "description": "图片裁剪", 6 | "main": "src/crop.js", 7 | "scripts": { 8 | "build": "node build.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/jinzhe/crop.git" 13 | }, 14 | "keywords": [ "crop" ], 15 | "author": "zee kim", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/jinzhe/crop/issues" 19 | }, 20 | "homepage": "https://zee.kim", 21 | "devDependencies": { 22 | "babel-core": "^6.22.1", 23 | "babel-preset-es2015-rollup": "^3.0.0", 24 | "rollup": "^0.41.4", 25 | "rollup-plugin-babel": "^2.7.1", 26 | "rollup-plugin-commonjs": "^7.0.0", 27 | "rollup-plugin-node-resolve": "^2.0.0", 28 | "rollup-plugin-uglify": "^1.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jinzhe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/crop.min.js: -------------------------------------------------------------------------------- 1 | var crop=function(){"use strict";var t=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},e=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};t(this,n),this.width=i.width||400,this.height=i.height||400,this.quality=i.quality||.8,this.success=i.success,this.style();this.layout=document.createElement("DIV"),this.layout.classList.add("crop"),this.layout.classList.add("active"),this.layout.innerHTML='\n
\n \n
\n
\n \n
\n \n \n \n \n
\n
\n
OK
\n ',document.querySelector("body").appendChild(this.layout),this.input=document.querySelector(i.input||".crop-file"),this.crop=document.querySelector(i.crop||".crop"),this.cropWall=document.querySelector(i.wall||".crop-wall"),this.cropImage=document.querySelector(i.image||".crop-image"),this.cropClose=document.querySelector(i.close||".crop-close"),this.cropOk=document.querySelector(i.ok||".crop-ok"),this.cropZoom=document.querySelector(i.zoom||".crop-zoom"),this.crop.style.width=this.width+"px",this.crop.style.height=this.height+"px",this.cropWall.style.width=this.width+"px",this.cropWall.style.height=this.height+"px",this.input.addEventListener("change",function(t){if(e.files=t.target.files||t.dataTransfer.files,0==e.files.length)return!1;e.getOrientation(e.files[0],function(t){e.orientation=t;var n=new FileReader;n.onload=function(t){e.open(e.dataURLtoBlob(t.target.result))},n.readAsDataURL(e.files[0])})}),void 0!=i.url&&this.open(i.url),this.cropClose.addEventListener("click",function(){e.layout.parentNode.removeChild(e.layout)}),this.canvas=document.createElement("canvas"),this.canvas.width=this.width,this.canvas.height=this.height,this.canvas.setAttribute("style","position:absolute;left:0;top:0;opacity:0;pointer-events:none;"),this.context=this.canvas.getContext("2d"),this.layout.appendChild(this.canvas)}return e(n,[{key:"style",value:function(){if(!document.querySelector("#cropStyle")){var t='\n .crop {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate3d(-50%, -50%, 0);\n width: 400px;\n height: 400px;\n background: #000;\n box-shadow: 0 0 20px 2px rgba(0, 0, 0, .5);\n z-index: 20;\n transition: all .2s cubic-bezier(0.99, 0.01, 0.22, 0.94);\n }\n\n .crop.loading::after {\n content: "LOADING";\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n background-color: rgba(0, 0, 0, .5);\n color: #666;\n }\n\n .crop-wall {\n position: relative;\n height: 100%;\n overflow: hidden;\n }\n .crop-wall img{\n user-select:none;\n }\n .crop-mask{\n pointer-events:none;\n position:absolute;\n left:0;\n right:0;\n top:0;\n bottom:0;\n }\n .crop-mask::before{\n content:"";\n position:absolute;\n left:0;\n right:0;\n top:0;\n height:120px;\n background-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);\n }\n .crop-mask::after{\n content:"";\n position:absolute;\n left:0;\n right:0;\n bottom:0;\n height:120px;\n background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%);\n }\n .crop-file {\n position: absolute;\n left: 15px;\n top: 15px;\n transition: .3s;\n cursor: pointer;\n }\n\n .crop-file:hover {\n transform: scale(1.2);\n }\n\n .crop-file svg {\n width: 22px;\n height: 22px;\n }\n\n .crop-file input {\n position: absolute;\n width: 100%;\n height: 100%;\n opacity: 0;\n cursor: pointer;\n }\n\n .crop.active .crop-file {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n transition: 0s;\n }\n\n .crop.loading .crop-file {\n opacity: 0;\n\n }\n\n .crop.active .crop-file svg {\n width: 50px;\n height: 50px;\n }\n\n .crop.active .crop-file svg path {\n fill: #333;\n }\n\n .crop-ok {\n position: absolute;\n right: 10px;\n bottom: 10px;\n width: 66px;\n height: 26px;\n border-radius: 40px;\n text-align: center;\n font-size: 10px;\n font-family: arial;\n line-height: 26px;\n border: 2px solid #fff;\n color: #fff;\n transition: all .2s ease-in-out;\n cursor: pointer;\n opacity: 0;\n }\n\n .crop-ok.show {\n opacity: 1;\n }\n\n .crop-ok:hover {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n .crop-close {\n position: absolute;\n top: 15px;\n right: 15px;\n width: 30px;\n height: 30px;\n cursor: pointer;\n transition: .3s;\n transform: scale(0.8);\n }\n\n .crop-close:hover {\n transform: scale(1);\n }\n\n .crop-close:before {\n position: absolute;\n content: \'\';\n width: 30px;\n height: 2px;\n background: white;\n transform: rotate(45deg);\n top: 14px;\n left: 0px;\n border-radius: 2px;\n }\n\n .crop-close:after {\n content: \'\';\n position: absolute;\n width: 30px;\n height: 2px;\n background: white;\n transform: rotate(-45deg);\n top: 14px;\n left: 0px;\n border-radius: 2px;\n }\n\n .crop-zoom {\n position: absolute;\n left: 10px;\n bottom: 20px;\n width: 100px;\n opacity: 0;\n transition: .3s;\n }\n\n .crop-zoom.show {\n opacity: 1;\n }\n\n input[type=range] {\n -webkit-appearance: none;\n\n }\n\n input[type=range]:focus {\n outline: none;\n }\n\n input[type=range]::-webkit-slider-runnable-track {\n width: 100%;\n height: 2px;\n border-radius: 2px;\n background: #fff;\n border: none;\n\n }\n\n input[type=range]::-webkit-slider-thumb {\n height: 16px;\n width: 16px;\n border-radius: 10px;\n background: #fff;\n cursor: pointer;\n -webkit-appearance: none;\n margin-top: -8px;\n transition: .3s;\n }\n\n input[type=range]::-webkit-slider-thumb:hover {\n transform: scale(1.2);\n }\n\n input[type=range]::-moz-range-track {\n width: 100%;\n height: 2px;\n background: #000;\n }\n\n input[type=range]::-moz-range-thumb {\n height: 16px;\n width: 16px;\n border-radius: 8px;\n border: 2px solid #efb708;\n background: #ffffff;\n cursor: pointer;\n }\n\n input[type=range]::-ms-track {\n width: 100%;\n height: 1px;\n cursor: pointer;\n background: transparent;\n border-color: transparent;\n color: transparent;\n }\n\n input[type=range]::-ms-fill-lower {\n background: rgba(0, 0, 0, 0.5);\n border: 0px solid rgba(200, 200, 200, 0.2);\n border-radius: 0px;\n box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0);\n }\n\n input[type=range]::-ms-fill-upper {\n background: rgba(0, 0, 0, 0.5);\n border: 0px solid rgba(200, 200, 200, 0.2);\n border-radius: 0px;\n box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0);\n }\n\n input[type=range]::-ms-thumb {\n height: 16px;\n width: 16px;\n border-radius: 8px;\n background: #ffffff;\n cursor: pointer;\n height: 1px;\n }\n\n input[type=range]:focus::-ms-fill-lower {\n background: rgba(0, 0, 0, 0.5);\n }\n\n input[type=range]:focus::-ms-fill-upper {\n background: rgba(0, 0, 0, 0.5);\n }\n ',e=document.createElement("style");e.type="text/css",e.id="cropStyle",e.styleSheet?e.styleSheet.cssText=t.replace(/\s/,""):e.innerHTML=t.replace(/\s/,""),document.getElementsByTagName("head")[0].appendChild(e)}}},{key:"open",value:function(t){var e=this,n=this;this.isMobile=/ios|android/.test(navigator.userAgent.toLowerCase()),this.beginX=0,this.beginY=0,this.scaleX=0,this.scaleY=0,this.scaleHeight=0,this.scaleWidth=0,this.isDrag=!1,this.crop.classList.add("loading"),this.img=new Image,this.img.crossOrigin="anonymous",this.img.onload=function(){if(e.crop.classList.remove("loading"),e.crop.classList.remove("active"),e.cropZoom.classList.add("show"),e.cropOk.classList.add("show"),e.temp={width:e.img.naturalWidth,height:e.img.naturalHeight},6==e.orientation&&(e.temp.width=e.img.naturalHeight,e.temp.height=e.img.naturalWidth),e.temp.width>e.temp.height?(e.scaleHeight=e.height,e.scaleWidth=Math.round(e.scaleHeight*e.temp.width/e.temp.height)):(e.scaleWidth=e.width,e.scaleHeight=Math.round(e.scaleWidth*e.temp.height/e.temp.width)),e.scaleX=-Math.round((e.scaleWidth-e.width)/2),e.scaleY=-Math.round((e.scaleHeight-e.height)/2),6==e.orientation?e.fixedOrientation(e.img,e.temp.width,e.temp.height,function(t){e.cropImage.setAttribute("src",t)}):e.cropImage.setAttribute("src",t),e.crop.style.width=e.width+"px",e.crop.style.height=e.height+"px",e.cropWall.style.width=e.width+"px",e.cropWall.style.height=e.height+"px",e.cropImage.style.position="absolute",e.cropImage.style.left=e.scaleX+"px",e.cropImage.style.top=e.scaleY+"px",e.cropImage.style.width=e.scaleWidth+"px",e.cropImage.style.height=e.scaleHeight+"px",e.cropImage.style.userSelect="none",e.cropZoom.value=100,e.cropCanvas(),!e.hasEvent){var i=function(){var t=e.canvas.toDataURL("image/jpeg",e.quality);e.success&&e.success(t.substr(23)),e.layout.parentNode.removeChild(e.layout)};e.cropZoom.addEventListener("input",function(t){n.zoom(t)}),e.cropOk.addEventListener("click",i),e.cropImage.addEventListener(n.isMobile?"touchstart":"mousedown",function(t){t.preventDefault(),n.isDrag=!0,n.beginX=t.pageX-t.target.offsetLeft,n.beginY=t.pageY-t.target.offsetTop;var e=function(t){if(!n.isDrag)return!1;n.isMobile?2==t.touches.length?(n.scaleLength=n.touchData(t).length,n.scale=Math.min(200,Math.max(100,n.scaleLength/n.beginLength*100)),this.zoom()):(n.scaleX=t.pageX-beginX,n.scaleY=t.pageY-beginY):(n.scaleX=t.pageX-n.beginX,n.scaleY=t.pageY-n.beginY),n.limit(),n.cropImage.style.width=n.scaleWidth+"px",n.cropImage.style.height=n.scaleHeight+"px",n.cropImage.style.left=n.scaleX+"px",n.cropImage.style.top=n.scaleY+"px"},i=function t(i){n.isDrag=!1,n.isMobile?(document.removeEventListener("touchmove",e,!1),document.removeEventListener("touchend",t,!1)):(document.removeEventListener("mousemove",e,!1),document.removeEventListener("mouseup",t,!1))};n.isMobile?(2==t.touches.length&&(n.beginLength=0==n.beginLength?n.touchData(t).length:n.beginLength),document.addEventListener("touchmove",e),document.addEventListener("touchend",i)):(document.addEventListener("mousemove",e),document.addEventListener("mouseup",i))}),e.hasEvent=!0}},this.img.src=t}},{key:"cropCanvas",value:function(){this.context.clearRect(0,0,this.width,this.height);var t=Math.round(Math.abs(this.scaleX)*this.img.naturalWidth/this.scaleWidth),e=Math.round(Math.abs(this.scaleY)*this.img.naturalHeight/this.scaleHeight),n=this.width*this.img.naturalWidth/this.scaleWidth>>0,i=this.height*this.img.naturalHeight/this.scaleHeight>>0;this.context.drawImage(this.img,t,e,n,i,0,0,this.width,this.height/this.cropRatio())}},{key:"cropRatio",value:function(){var t=(this.img.naturalWidth,this.img.naturalHeight),e=document.createElement("canvas");e.width=1,e.height=t;var n=e.getContext("2d");n.drawImage(this.img,0,0);for(var i=n.getImageData(0,0,1,t).data,o=0,a=t,s=t;s>o;){0===i[4*(s-1)+3]?a=s:o=s,s=a+o>>1}var r=s/t;return 0===r?1:r}},{key:"getOrientation",value:function(t,e){var n=new FileReader;n.onload=function(t){var n=new DataView(t.target.result);if(65496!=n.getUint16(0,!1))return e(-2);for(var i=n.byteLength,o=2;othis.temp.height?(o=this.scaleHeight/this.height,this.scaleHeight=this.height*e,this.scaleWidth=this.scaleHeight*this.temp.width/this.temp.height):(o=this.scaleWidth/this.width,this.scaleWidth=this.width*e,this.scaleHeight=this.scaleWidth*this.temp.height/this.temp.width),this.scaleX=n*e/o+this.width/2,this.scaleY=i*e/o+this.height/2,this.limit(),this.cropCanvas(),this.cropImage.style.width=this.scaleWidth+"px",this.cropImage.style.height=this.scaleHeight+"px",this.cropImage.style.left=this.scaleX+"px",this.cropImage.style.top=this.scaleY+"px"}},{key:"limit",value:function(){this.scaleX<-(this.scaleWidth-this.width)&&(this.scaleX=-(this.scaleWidth-this.width)),this.scaleY<-(this.scaleHeight-this.height)&&(this.scaleY=-(this.scaleHeight-this.height)),this.scaleX>0&&(this.scaleX=0),this.scaleY>0&&(this.scaleY=0),this.scaleHeight===this.height&&(this.scaleY=0),this.scaleWidth===this.width&&(this.scaleX=0)}},{key:"touchData",value:function(t){if(!(t.touches.length<2)){var e=t.touches[0].pageX,n=t.touches[1].pageX,i=e<=n?(n-e)/2+e:(e-n)/2+n,o=t.touches[0].pageY-this.scrollbar.scrollTop,a=t.touches[1].pageY-this.scrollbar.scrollTop,s=o<=a?(a-o)/2+o:(o-a)/2+a;return{length:Math.round(Math.sqrt(Math.pow(e-n,2)+Math.pow(o-a,2))),x:Math.round(i),y:Math.round(s)}}}},{key:"dataURLtoBlob",value:function(t){for(var e=t.split(","),n=e[0].match(/:(.*?);/)[1],i=atob(e[1]),o=i.length,a=new Uint8Array(o);o--;)a[o]=i.charCodeAt(o);return URL.createObjectURL(new Blob([a],{type:n}))}}]),n}()}(); 2 | -------------------------------------------------------------------------------- /src/crop.js: -------------------------------------------------------------------------------- 1 | export default class crop { 2 | constructor(options = {}) { 3 | this.width = options.width || 400; 4 | this.height = options.height || 400; 5 | this.quality = options.quality || 0.8; 6 | this.success = options.success; 7 | this.style(); 8 | const html = ` 9 |
10 | 11 |
12 |
13 | 14 |
15 | 16 | 17 | 19 | 20 |
21 |
22 |
OK
23 | `; 24 | this.layout = document.createElement('DIV'); 25 | this.layout.classList.add("crop"); 26 | this.layout.classList.add("active"); 27 | this.layout.innerHTML = html; 28 | document.querySelector("body").appendChild(this.layout); 29 | 30 | this.input = document.querySelector(options.input || ".crop-file"); 31 | this.crop = document.querySelector(options.crop || ".crop"); 32 | this.cropWall = document.querySelector(options.wall || ".crop-wall"); 33 | this.cropImage = document.querySelector(options.image || ".crop-image"); 34 | this.cropClose = document.querySelector(options.close || ".crop-close"); 35 | this.cropOk = document.querySelector(options.ok || ".crop-ok"); 36 | this.cropZoom = document.querySelector(options.zoom || ".crop-zoom"); 37 | 38 | this.crop.style.width = this.width + "px"; 39 | this.crop.style.height = this.height + "px"; 40 | this.cropWall.style.width = this.width + "px"; 41 | this.cropWall.style.height = this.height + "px"; 42 | 43 | this.input.addEventListener("change", (e) => { 44 | this.files = e.target.files || e.dataTransfer.files; 45 | if (this.files.length == 0) return false; 46 | this.getOrientation(this.files[0], (o) => { 47 | this.orientation = o; 48 | var reader = new FileReader(); 49 | reader.onload = (file) => { 50 | this.open(this.dataURLtoBlob(file.target.result)); 51 | }; 52 | reader.readAsDataURL(this.files[0]); 53 | }) 54 | }); 55 | if (options.url != undefined) { 56 | this.open(options.url); 57 | } 58 | 59 | this.cropClose.addEventListener("click", () => { 60 | this.layout.parentNode.removeChild(this.layout); 61 | }); 62 | 63 | // 渲染到canvas 64 | this.canvas = document.createElement("canvas") 65 | this.canvas.width = this.width 66 | this.canvas.height = this.height 67 | this.canvas.setAttribute("style", "position:absolute;left:0;top:0;opacity:0;pointer-events:none;") 68 | this.context = this.canvas.getContext("2d") 69 | 70 | this.layout.appendChild(this.canvas); 71 | 72 | } 73 | 74 | style() { 75 | if (document.querySelector("#cropStyle")) { 76 | return; 77 | } 78 | const css = ` 79 | .crop { 80 | position: absolute; 81 | left: 50%; 82 | top: 50%; 83 | transform: translate3d(-50%, -50%, 0); 84 | width: 400px; 85 | height: 400px; 86 | background: #000; 87 | box-shadow: 0 0 20px 2px rgba(0, 0, 0, .5); 88 | z-index: 20; 89 | transition: all .2s cubic-bezier(0.99, 0.01, 0.22, 0.94); 90 | } 91 | 92 | .crop.loading::after { 93 | content: "LOADING"; 94 | position: absolute; 95 | left: 0; 96 | right: 0; 97 | top: 0; 98 | bottom: 0; 99 | display: flex; 100 | align-items: center; 101 | justify-content: center; 102 | font-size: 10px; 103 | background-color: rgba(0, 0, 0, .5); 104 | color: #666; 105 | } 106 | .crop-wall { 107 | position: relative; 108 | height: 100%; 109 | overflow: hidden; 110 | } 111 | .crop-wall img{ 112 | user-select:none; 113 | } 114 | .crop-mask{ 115 | pointer-events:none; 116 | position:absolute; 117 | left:0; 118 | right:0; 119 | top:0; 120 | bottom:0; 121 | } 122 | .crop-mask::before{ 123 | content:""; 124 | position:absolute; 125 | left:0; 126 | right:0; 127 | top:0; 128 | height:120px; 129 | background-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%); 130 | } 131 | .crop-mask::after{ 132 | content:""; 133 | position:absolute; 134 | left:0; 135 | right:0; 136 | bottom:0; 137 | height:120px; 138 | background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%); 139 | } 140 | .crop-file { 141 | position: absolute; 142 | left: 15px; 143 | top: 15px; 144 | transition: .3s; 145 | cursor: pointer; 146 | } 147 | 148 | .crop-file:hover { 149 | transform: scale(1.2); 150 | } 151 | 152 | .crop-file svg { 153 | width: 22px; 154 | height: 22px; 155 | } 156 | 157 | .crop-file input { 158 | position: absolute; 159 | width: 100%; 160 | height: 100%; 161 | opacity: 0; 162 | cursor: pointer; 163 | } 164 | .crop.active .crop-file { 165 | position: absolute; 166 | left: 50%; 167 | top: 50%; 168 | transform: translate(-50%, -50%); 169 | transition: 0s; 170 | } 171 | .crop.loading .crop-file { 172 | opacity: 0; 173 | } 174 | .crop.active .crop-file svg { 175 | width: 50px; 176 | height: 50px; 177 | } 178 | .crop.active .crop-file svg path { 179 | fill: #333; 180 | } 181 | .crop-ok { 182 | position: absolute; 183 | right: 10px; 184 | bottom: 10px; 185 | width: 66px; 186 | height: 26px; 187 | border-radius: 40px; 188 | text-align: center; 189 | font-size: 10px; 190 | font-family: arial; 191 | line-height: 26px; 192 | border: 2px solid #fff; 193 | color: #fff; 194 | transition: all .2s ease-in-out; 195 | cursor: pointer; 196 | opacity: 0; 197 | } 198 | .crop-ok.show { 199 | opacity: 1; 200 | } 201 | .crop-ok:hover { 202 | background-color: rgba(255, 255, 255, 0.2); 203 | } 204 | .crop-close { 205 | position: absolute; 206 | top: 15px; 207 | right: 15px; 208 | width: 30px; 209 | height: 30px; 210 | cursor: pointer; 211 | transition: .3s; 212 | transform: scale(0.8); 213 | } 214 | .crop-close:hover { 215 | transform: scale(1); 216 | } 217 | .crop-close:before { 218 | position: absolute; 219 | content: ''; 220 | width: 30px; 221 | height: 2px; 222 | background: white; 223 | transform: rotate(45deg); 224 | top: 14px; 225 | left: 0px; 226 | border-radius: 2px; 227 | } 228 | .crop-close:after { 229 | content: ''; 230 | position: absolute; 231 | width: 30px; 232 | height: 2px; 233 | background: white; 234 | transform: rotate(-45deg); 235 | top: 14px; 236 | left: 0px; 237 | border-radius: 2px; 238 | } 239 | .crop-zoom { 240 | position: absolute; 241 | left: 10px; 242 | bottom: 20px; 243 | width: 100px; 244 | opacity: 0; 245 | transition: .3s; 246 | } 247 | .crop-zoom.show { 248 | opacity: 1; 249 | } 250 | input[type=range] { 251 | -webkit-appearance: none; 252 | } 253 | input[type=range]:focus { 254 | outline: none; 255 | } 256 | input[type=range]::-webkit-slider-runnable-track { 257 | width: 100%; 258 | height: 2px; 259 | border-radius: 2px; 260 | background: #fff; 261 | border: none; 262 | } 263 | input[type=range]::-webkit-slider-thumb { 264 | height: 16px; 265 | width: 16px; 266 | border-radius: 10px; 267 | background: #fff; 268 | cursor: pointer; 269 | -webkit-appearance: none; 270 | margin-top: -8px; 271 | transition: .3s; 272 | } 273 | input[type=range]::-webkit-slider-thumb:hover { 274 | transform: scale(1.2); 275 | } 276 | input[type=range]::-moz-range-track { 277 | width: 100%; 278 | height: 2px; 279 | border-radius: 2px; 280 | background: #fff; 281 | border: none; 282 | } 283 | input[type=range]::-moz-range-thumb { 284 | height: 16px; 285 | width: 16px; 286 | border-radius: 10px; 287 | background: #fff; 288 | cursor: pointer; 289 | -moz-appearance: none; 290 | margin-top: -8px; 291 | transition: .3s; 292 | } 293 | 294 | input[type=range]::-ms-track { 295 | width: 100%; 296 | height: 2px; 297 | border-radius: 2px; 298 | background: #fff; 299 | border: none; 300 | } 301 | 302 | input[type=range]::-ms-fill-lower { 303 | background: rgba(0, 0, 0, 0.5); 304 | border: 0px solid rgba(200, 200, 200, 0.2); 305 | border-radius: 0px; 306 | box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0); 307 | } 308 | 309 | input[type=range]::-ms-fill-upper { 310 | background: rgba(0, 0, 0, 0.5); 311 | border: 0px solid rgba(200, 200, 200, 0.2); 312 | border-radius: 0px; 313 | box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0); 314 | } 315 | 316 | input[type=range]::-ms-thumb { 317 | height: 16px; 318 | width: 16px; 319 | border-radius: 10px; 320 | background: #fff; 321 | cursor: pointer; 322 | margin-top: -8px; 323 | transition: .3s; 324 | } 325 | 326 | input[type=range]:focus::-ms-fill-lower { 327 | background: rgba(0, 0, 0, 0.5); 328 | } 329 | 330 | input[type=range]:focus::-ms-fill-upper { 331 | background: rgba(0, 0, 0, 0.5); 332 | } 333 | `; 334 | const node = document.createElement("style"); 335 | node.type = "text/css"; 336 | node.id = "cropStyle" 337 | if (node.styleSheet) { 338 | node.styleSheet.cssText = css.replace(/\s/, ""); 339 | } else { 340 | node.innerHTML = css.replace(/\s/, ""); 341 | } 342 | document.getElementsByTagName("head")[0].appendChild(node); 343 | } 344 | // 打开裁剪 345 | open(url) { 346 | const that = this; 347 | this.isMobile = /ios|android/.test(navigator.userAgent.toLowerCase()); 348 | this.beginX = 0; 349 | this.beginY = 0; 350 | this.scaleX = 0; 351 | this.scaleY = 0; 352 | this.scaleHeight = 0; 353 | this.scaleWidth = 0; 354 | this.isDrag = false; 355 | 356 | this.crop.classList.add("loading"); 357 | 358 | this.img = new Image(); 359 | this.img.crossOrigin = 'anonymous'; 360 | this.img.onload = () => { 361 | this.crop.classList.remove("loading"); 362 | this.crop.classList.remove("active"); 363 | this.cropZoom.classList.add("show"); 364 | this.cropOk.classList.add("show"); 365 | // alert(this.img.naturalWidth + "/" + this.img.naturalHeight) 366 | // 按原始图片比例优先使用小值设置最低值 367 | this.temp = { 368 | width: this.img.naturalWidth, 369 | height: this.img.naturalHeight 370 | }; 371 | if (this.orientation == 6) { 372 | this.temp.width = this.img.naturalHeight; 373 | this.temp.height = this.img.naturalWidth; 374 | } 375 | if (this.temp.width > this.temp.height) { 376 | this.scaleHeight = this.height; 377 | this.scaleWidth = Math.round(this.scaleHeight * this.temp.width / this.temp.height); 378 | } else { 379 | this.scaleWidth = this.width; 380 | this.scaleHeight = Math.round(this.scaleWidth * this.temp.height / this.temp.width); 381 | } 382 | 383 | // console.log(scaleX,scaleY,scaleWidth,scaleHeight); 384 | this.scaleX = -Math.round((this.scaleWidth - this.width) / 2); 385 | this.scaleY = -Math.round((this.scaleHeight - this.height) / 2); 386 | if (this.orientation == 6) { 387 | this.fixedOrientation(this.img, this.temp.width, this.temp.height, (src) => { 388 | this.cropImage.setAttribute("src", src); 389 | }) 390 | } else { 391 | 392 | this.cropImage.setAttribute("src", url); 393 | } 394 | 395 | this.crop.style.width = this.width + "px"; 396 | this.crop.style.height = this.height + "px"; 397 | this.cropWall.style.width = this.width + "px"; 398 | this.cropWall.style.height = this.height + "px"; 399 | 400 | this.cropImage.style.position = "absolute"; 401 | this.cropImage.style.left = this.scaleX + "px"; 402 | this.cropImage.style.top = this.scaleY + "px"; 403 | this.cropImage.style.width = this.scaleWidth + "px"; 404 | this.cropImage.style.height = this.scaleHeight + "px"; 405 | this.cropImage.style.userSelect = "none"; 406 | this.cropZoom.value = 100; 407 | 408 | this.cropCanvas(); 409 | if (!this.hasEvent) { 410 | let okHander = () => { 411 | let data = this.canvas.toDataURL("image/jpeg", this.quality); 412 | this.success && this.success(data.substr(23)); 413 | this.layout.parentNode.removeChild(this.layout); 414 | } 415 | 416 | this.cropZoom.addEventListener("input", function (e) { 417 | that.zoom(e); 418 | }); 419 | this.cropOk.addEventListener("click", okHander); 420 | 421 | this.cropImage.addEventListener(that.isMobile ? "touchstart" : "mousedown", function (e) { 422 | e.preventDefault(); 423 | that.isDrag = true 424 | that.beginX = e.pageX - e.target.offsetLeft 425 | that.beginY = e.pageY - e.target.offsetTop 426 | // 拖动鼠标 427 | const move = function (e) { 428 | // console.log("move",e) 429 | // e.preventDefault(); 430 | 431 | if (!that.isDrag) return false 432 | // 放大 433 | if (that.isMobile) { 434 | if (e.touches.length == 2) { 435 | that.scaleLength = that.touchData(e).length 436 | that.scale = Math.min(200, Math.max(100, that.scaleLength / that.beginLength * 100)) 437 | this.zoom() 438 | } else { 439 | that.scaleX = e.pageX - beginX 440 | that.scaleY = e.pageY - beginY 441 | 442 | } 443 | } else { 444 | that.scaleX = e.pageX - that.beginX 445 | that.scaleY = e.pageY - that.beginY 446 | } 447 | that.limit() 448 | that.cropImage.style.width = that.scaleWidth + "px"; 449 | that.cropImage.style.height = that.scaleHeight + "px"; 450 | that.cropImage.style.left = that.scaleX + "px"; 451 | that.cropImage.style.top = that.scaleY + "px"; 452 | } 453 | 454 | // 放开那个鼠标 455 | const end = function (e) { 456 | // e.preventDefault(); 457 | that.isDrag = false 458 | if (that.isMobile) { 459 | document.removeEventListener('touchmove', move, false) 460 | document.removeEventListener('touchend', end, false) 461 | } else { 462 | document.removeEventListener('mousemove', move, false) 463 | document.removeEventListener('mouseup', end, false) 464 | } 465 | } 466 | if (that.isMobile) { 467 | if (e.touches.length == 2) { 468 | that.beginLength = that.beginLength == 0 ? that.touchData(e).length : that.beginLength 469 | } 470 | document.addEventListener('touchmove', move); 471 | document.addEventListener('touchend', end); 472 | 473 | } else { 474 | document.addEventListener('mousemove', move); 475 | document.addEventListener('mouseup', end); 476 | } 477 | 478 | }); 479 | this.hasEvent = true; 480 | } 481 | } 482 | this.img.src = url; 483 | } 484 | // 粘贴到canvas 485 | cropCanvas() { 486 | this.context.clearRect(0, 0, this.width, this.height) 487 | let sx = Math.round(Math.abs(this.scaleX) * this.img.naturalWidth / this.scaleWidth) //图像源x坐标 488 | let sy = Math.round(Math.abs(this.scaleY) * this.img.naturalHeight / this.scaleHeight) //图像源y坐标 489 | let sw = (this.width * this.img.naturalWidth / this.scaleWidth) >> 0 490 | let sh = (this.height * this.img.naturalHeight / this.scaleHeight) >> 0 491 | this.context.drawImage(this.img, sx, sy, sw, sh, 0, 0, this.width, this.height / this.cropRatio()) 492 | } 493 | 494 | // http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios 495 | // 修正 iPhone 上传的方向问题 496 | cropRatio() { 497 | var iw = this.img.naturalWidth, ih = this.img.naturalHeight; 498 | var canvas = document.createElement('canvas'); 499 | canvas.width = 1; 500 | canvas.height = ih; 501 | var ctx = canvas.getContext('2d'); 502 | ctx.drawImage(this.img, 0, 0); 503 | var data = ctx.getImageData(0, 0, 1, ih).data; 504 | // search image edge pixel position in case it is squashed vertically. 505 | var sy = 0; 506 | var ey = ih; 507 | var py = ih; 508 | while (py > sy) { 509 | var alpha = data[(py - 1) * 4 + 3]; 510 | if (alpha === 0) { 511 | ey = py; 512 | } else { 513 | sy = py; 514 | } 515 | py = (ey + sy) >> 1; 516 | } 517 | var ratio = (py / ih); 518 | return (ratio === 0) ? 1 : ratio; 519 | } 520 | 521 | getOrientation(file, callback) { 522 | var reader = new FileReader(); 523 | reader.onload = function (e) { 524 | 525 | var view = new DataView(e.target.result); 526 | if (view.getUint16(0, false) != 0xFFD8) return callback(-2); 527 | var length = view.byteLength, offset = 2; 528 | while (offset < length) { 529 | var marker = view.getUint16(offset, false); 530 | offset += 2; 531 | if (marker == 0xFFE1) { 532 | if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1); 533 | var little = view.getUint16(offset += 6, false) == 0x4949; 534 | offset += view.getUint32(offset + 4, little); 535 | var tags = view.getUint16(offset, little); 536 | offset += 2; 537 | for (var i = 0; i < tags; i++) 538 | if (view.getUint16(offset + (i * 12), little) == 0x0112) 539 | return callback(view.getUint16(offset + (i * 12) + 8, little)); 540 | } 541 | else if ((marker & 0xFF00) != 0xFF00) break; 542 | else offset += view.getUint16(offset, false); 543 | } 544 | return callback(-1); 545 | }; 546 | reader.readAsArrayBuffer(file.slice(0, 64 * 1024)); 547 | } 548 | 549 | //图片压缩 550 | fixedOrientation(img, width, height, callback) { 551 | var canvas, ctx; 552 | canvas = document.createElement('canvas'); 553 | canvas.width = width; 554 | canvas.height = height; 555 | ctx = canvas.getContext("2d"); 556 | //如果图片方向等于6 ,则旋转矫正,反之则不做处理 557 | if (this.orientation == 6) { 558 | ctx.save(); 559 | ctx.translate(width / 2, height / 2); 560 | ctx.rotate(90 * Math.PI / 180); 561 | ctx.drawImage(img, 0 - height / 2, 0 - width / 2, height, width); 562 | ctx.restore(); 563 | } else { 564 | ctx.drawImage(img, 0, 0, width, height); 565 | } 566 | canvas.toBlob(function (blob) { 567 | let url = URL.createObjectURL(blob); 568 | callback && callback(url); 569 | }); 570 | 571 | } 572 | // 放大缩小 573 | zoom(e) { 574 | let s = parseInt(e.target.value) / 100; 575 | let osx = this.scaleX - this.width / 2; 576 | let osy = this.scaleY - this.height / 2; 577 | let os; 578 | // 按原始图片比例优先使用小值设置最低值 579 | if (this.temp.width > this.temp.height) { 580 | os = this.scaleHeight / this.height; 581 | this.scaleHeight = this.height * s; 582 | this.scaleWidth = this.scaleHeight * this.temp.width / this.temp.height; 583 | } else { 584 | os = this.scaleWidth / this.width; 585 | this.scaleWidth = this.width * s; 586 | this.scaleHeight = this.scaleWidth * this.temp.height / this.temp.width; 587 | } 588 | this.scaleX = (osx) * s / os + this.width / 2; 589 | this.scaleY = (osy) * s / os + this.height / 2; 590 | this.limit(); 591 | this.cropCanvas() 592 | this.cropImage.style.width = this.scaleWidth + "px"; 593 | this.cropImage.style.height = this.scaleHeight + "px"; 594 | this.cropImage.style.left = this.scaleX + "px"; 595 | this.cropImage.style.top = this.scaleY + "px"; 596 | } 597 | // 限制拖动检测边缘 598 | limit() { 599 | if (this.scaleX < -(this.scaleWidth - this.width)) this.scaleX = -(this.scaleWidth - this.width) 600 | if (this.scaleY < -(this.scaleHeight - this.height)) this.scaleY = -(this.scaleHeight - this.height) 601 | if (this.scaleX > 0) this.scaleX = 0 602 | if (this.scaleY > 0) this.scaleY = 0 603 | if (this.scaleHeight === this.height) this.scaleY = 0 604 | if (this.scaleWidth === this.width) this.scaleX = 0 605 | } 606 | 607 | // 获取多点触控 608 | touchData(e) { 609 | if (e.touches.length < 2) return 610 | let x1 = e.touches[0].pageX 611 | let x2 = e.touches[1].pageX 612 | let x3 = (x1 <= x2 ? (x2 - x1) / 2 + x1 : (x1 - x2) / 2 + x2) 613 | let y1 = e.touches[0].pageY - this.scrollbar.scrollTop 614 | let y2 = e.touches[1].pageY - this.scrollbar.scrollTop 615 | let y3 = (y1 <= y2 ? (y2 - y1) / 2 + y1 : (y1 - y2) / 2 + y2) 616 | return { 617 | length: Math.round(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))), 618 | x: Math.round(x3), 619 | y: Math.round(y3) 620 | } 621 | } 622 | 623 | dataURLtoBlob(dataurl) { 624 | let arr = dataurl.split(','); 625 | let mime = arr[0].match(/:(.*?);/)[1]; 626 | let bstr = atob(arr[1]); 627 | let n = bstr.length, u8arr = new Uint8Array(n); 628 | while (n--) { 629 | u8arr[n] = bstr.charCodeAt(n); 630 | } 631 | return URL.createObjectURL(new Blob([u8arr], { type: mime })); 632 | } 633 | 634 | } -------------------------------------------------------------------------------- /dist/crop.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.crop = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var classCallCheck = function (instance, Constructor) { 8 | if (!(instance instanceof Constructor)) { 9 | throw new TypeError("Cannot call a class as a function"); 10 | } 11 | }; 12 | 13 | var createClass = function () { 14 | function defineProperties(target, props) { 15 | for (var i = 0; i < props.length; i++) { 16 | var descriptor = props[i]; 17 | descriptor.enumerable = descriptor.enumerable || false; 18 | descriptor.configurable = true; 19 | if ("value" in descriptor) descriptor.writable = true; 20 | Object.defineProperty(target, descriptor.key, descriptor); 21 | } 22 | } 23 | 24 | return function (Constructor, protoProps, staticProps) { 25 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 26 | if (staticProps) defineProperties(Constructor, staticProps); 27 | return Constructor; 28 | }; 29 | }(); 30 | 31 | var crop = function () { 32 | function crop() { 33 | var _this = this; 34 | 35 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 36 | classCallCheck(this, crop); 37 | 38 | this.width = options.width || 400; 39 | this.height = options.height || 400; 40 | this.quality = options.quality || 0.8; 41 | this.success = options.success; 42 | this.style(); 43 | var html = "\n
\n \n
\n
\n \n
\n \n \n \n \n
\n
\n
OK
\n "; 44 | this.layout = document.createElement('DIV'); 45 | this.layout.classList.add("crop"); 46 | this.layout.classList.add("active"); 47 | this.layout.innerHTML = html; 48 | document.querySelector("body").appendChild(this.layout); 49 | 50 | this.input = document.querySelector(options.input || ".crop-file"); 51 | this.crop = document.querySelector(options.crop || ".crop"); 52 | this.cropWall = document.querySelector(options.wall || ".crop-wall"); 53 | this.cropImage = document.querySelector(options.image || ".crop-image"); 54 | this.cropClose = document.querySelector(options.close || ".crop-close"); 55 | this.cropOk = document.querySelector(options.ok || ".crop-ok"); 56 | this.cropZoom = document.querySelector(options.zoom || ".crop-zoom"); 57 | this.crop.style.width = this.width + "px"; 58 | this.crop.style.height = this.height + "px"; 59 | this.cropWall.style.width = this.width + "px"; 60 | this.cropWall.style.height = this.height + "px"; 61 | this.input.addEventListener("change", function (e) { 62 | _this.files = e.target.files || e.dataTransfer.files; 63 | if (_this.files.length == 0) return false; 64 | _this.getOrientation(_this.files[0], function (o) { 65 | _this.orientation = o; 66 | var reader = new FileReader(); 67 | reader.onload = function (file) { 68 | _this.open(_this.dataURLtoBlob(file.target.result)); 69 | }; 70 | reader.readAsDataURL(_this.files[0]); 71 | }); 72 | }); 73 | if (options.url != undefined) { 74 | this.open(options.url); 75 | } 76 | 77 | this.cropClose.addEventListener("click", function () { 78 | _this.layout.parentNode.removeChild(_this.layout); 79 | }); 80 | 81 | // 渲染到canvas 82 | this.canvas = document.createElement("canvas"); 83 | this.canvas.width = this.width; 84 | this.canvas.height = this.height; 85 | this.canvas.setAttribute("style", "position:absolute;left:0;top:0;opacity:0;pointer-events:none;"); 86 | this.context = this.canvas.getContext("2d"); 87 | 88 | this.layout.appendChild(this.canvas); 89 | } 90 | 91 | createClass(crop, [{ 92 | key: "style", 93 | value: function style() { 94 | if (document.querySelector("#cropStyle")) { 95 | return; 96 | } 97 | var css = "\n .crop {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate3d(-50%, -50%, 0);\n width: 400px;\n height: 400px;\n background: #000;\n box-shadow: 0 0 20px 2px rgba(0, 0, 0, .5);\n z-index: 20;\n transition: all .2s cubic-bezier(0.99, 0.01, 0.22, 0.94);\n }\n\n .crop.loading::after {\n content: \"LOADING\";\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n background-color: rgba(0, 0, 0, .5);\n color: #666;\n }\n\n .crop-wall {\n position: relative;\n height: 100%;\n overflow: hidden;\n }\n .crop-wall img{\n user-select:none;\n }\n .crop-mask{\n pointer-events:none;\n position:absolute;\n left:0;\n right:0;\n top:0;\n bottom:0;\n }\n .crop-mask::before{\n content:\"\";\n position:absolute;\n left:0;\n right:0;\n top:0;\n height:120px;\n background-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);\n }\n .crop-mask::after{\n content:\"\";\n position:absolute;\n left:0;\n right:0;\n bottom:0;\n height:120px;\n background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%);\n }\n .crop-file {\n position: absolute;\n left: 15px;\n top: 15px;\n transition: .3s;\n cursor: pointer;\n }\n\n .crop-file:hover {\n transform: scale(1.2);\n }\n\n .crop-file svg {\n width: 22px;\n height: 22px;\n }\n\n .crop-file input {\n position: absolute;\n width: 100%;\n height: 100%;\n opacity: 0;\n cursor: pointer;\n }\n\n .crop.active .crop-file {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n transition: 0s;\n }\n\n .crop.loading .crop-file {\n opacity: 0;\n\n }\n\n .crop.active .crop-file svg {\n width: 50px;\n height: 50px;\n }\n\n .crop.active .crop-file svg path {\n fill: #333;\n }\n\n .crop-ok {\n position: absolute;\n right: 10px;\n bottom: 10px;\n width: 66px;\n height: 26px;\n border-radius: 40px;\n text-align: center;\n font-size: 10px;\n font-family: arial;\n line-height: 26px;\n border: 2px solid #fff;\n color: #fff;\n transition: all .2s ease-in-out;\n cursor: pointer;\n opacity: 0;\n }\n\n .crop-ok.show {\n opacity: 1;\n }\n\n .crop-ok:hover {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n .crop-close {\n position: absolute;\n top: 15px;\n right: 15px;\n width: 30px;\n height: 30px;\n cursor: pointer;\n transition: .3s;\n transform: scale(0.8);\n }\n\n .crop-close:hover {\n transform: scale(1);\n }\n\n .crop-close:before {\n position: absolute;\n content: '';\n width: 30px;\n height: 2px;\n background: white;\n transform: rotate(45deg);\n top: 14px;\n left: 0px;\n border-radius: 2px;\n }\n\n .crop-close:after {\n content: '';\n position: absolute;\n width: 30px;\n height: 2px;\n background: white;\n transform: rotate(-45deg);\n top: 14px;\n left: 0px;\n border-radius: 2px;\n }\n\n .crop-zoom {\n position: absolute;\n left: 10px;\n bottom: 20px;\n width: 100px;\n opacity: 0;\n transition: .3s;\n }\n\n .crop-zoom.show {\n opacity: 1;\n }\n\n input[type=range] {\n -webkit-appearance: none;\n\n }\n\n input[type=range]:focus {\n outline: none;\n }\n\n input[type=range]::-webkit-slider-runnable-track {\n width: 100%;\n height: 2px;\n border-radius: 2px;\n background: #fff;\n border: none;\n\n }\n\n input[type=range]::-webkit-slider-thumb {\n height: 16px;\n width: 16px;\n border-radius: 10px;\n background: #fff;\n cursor: pointer;\n -webkit-appearance: none;\n margin-top: -8px;\n transition: .3s;\n }\n\n input[type=range]::-webkit-slider-thumb:hover {\n transform: scale(1.2);\n }\n\n input[type=range]::-moz-range-track {\n width: 100%;\n height: 2px;\n background: #000;\n }\n\n input[type=range]::-moz-range-thumb {\n height: 16px;\n width: 16px;\n border-radius: 8px;\n border: 2px solid #efb708;\n background: #ffffff;\n cursor: pointer;\n }\n\n input[type=range]::-ms-track {\n width: 100%;\n height: 1px;\n cursor: pointer;\n background: transparent;\n border-color: transparent;\n color: transparent;\n }\n\n input[type=range]::-ms-fill-lower {\n background: rgba(0, 0, 0, 0.5);\n border: 0px solid rgba(200, 200, 200, 0.2);\n border-radius: 0px;\n box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0);\n }\n\n input[type=range]::-ms-fill-upper {\n background: rgba(0, 0, 0, 0.5);\n border: 0px solid rgba(200, 200, 200, 0.2);\n border-radius: 0px;\n box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0);\n }\n\n input[type=range]::-ms-thumb {\n height: 16px;\n width: 16px;\n border-radius: 8px;\n background: #ffffff;\n cursor: pointer;\n height: 1px;\n }\n\n input[type=range]:focus::-ms-fill-lower {\n background: rgba(0, 0, 0, 0.5);\n }\n\n input[type=range]:focus::-ms-fill-upper {\n background: rgba(0, 0, 0, 0.5);\n }\n "; 98 | var node = document.createElement("style"); 99 | node.type = "text/css"; 100 | node.id = "cropStyle"; 101 | if (node.styleSheet) { 102 | node.styleSheet.cssText = css.replace(/\s/, ""); 103 | } else { 104 | node.innerHTML = css.replace(/\s/, ""); 105 | } 106 | document.getElementsByTagName("head")[0].appendChild(node); 107 | } 108 | // 打开裁剪 109 | 110 | }, { 111 | key: "open", 112 | value: function open(url) { 113 | var _this2 = this; 114 | 115 | var that = this; 116 | this.isMobile = /ios|android/.test(navigator.userAgent.toLowerCase()); 117 | this.beginX = 0; 118 | this.beginY = 0; 119 | this.scaleX = 0; 120 | this.scaleY = 0; 121 | this.scaleHeight = 0; 122 | this.scaleWidth = 0; 123 | this.isDrag = false; 124 | 125 | this.crop.classList.add("loading"); 126 | 127 | this.img = new Image(); 128 | this.img.crossOrigin = 'anonymous'; 129 | this.img.onload = function () { 130 | _this2.crop.classList.remove("loading"); 131 | _this2.crop.classList.remove("active"); 132 | _this2.cropZoom.classList.add("show"); 133 | _this2.cropOk.classList.add("show"); 134 | // alert(this.img.naturalWidth + "/" + this.img.naturalHeight) 135 | // 按原始图片比例优先使用小值设置最低值 136 | _this2.temp = { 137 | width: _this2.img.naturalWidth, 138 | height: _this2.img.naturalHeight 139 | }; 140 | if (_this2.orientation == 6) { 141 | _this2.temp.width = _this2.img.naturalHeight; 142 | _this2.temp.height = _this2.img.naturalWidth; 143 | } 144 | if (_this2.temp.width > _this2.temp.height) { 145 | _this2.scaleHeight = _this2.height; 146 | _this2.scaleWidth = Math.round(_this2.scaleHeight * _this2.temp.width / _this2.temp.height); 147 | } else { 148 | _this2.scaleWidth = _this2.width; 149 | _this2.scaleHeight = Math.round(_this2.scaleWidth * _this2.temp.height / _this2.temp.width); 150 | } 151 | 152 | // console.log(scaleX,scaleY,scaleWidth,scaleHeight); 153 | _this2.scaleX = -Math.round((_this2.scaleWidth - _this2.width) / 2); 154 | _this2.scaleY = -Math.round((_this2.scaleHeight - _this2.height) / 2); 155 | if (_this2.orientation == 6) { 156 | _this2.fixedOrientation(_this2.img, _this2.temp.width, _this2.temp.height, function (src) { 157 | _this2.cropImage.setAttribute("src", src); 158 | }); 159 | } else { 160 | 161 | _this2.cropImage.setAttribute("src", url); 162 | } 163 | 164 | _this2.crop.style.width = _this2.width + "px"; 165 | _this2.crop.style.height = _this2.height + "px"; 166 | _this2.cropWall.style.width = _this2.width + "px"; 167 | _this2.cropWall.style.height = _this2.height + "px"; 168 | 169 | _this2.cropImage.style.position = "absolute"; 170 | _this2.cropImage.style.left = _this2.scaleX + "px"; 171 | _this2.cropImage.style.top = _this2.scaleY + "px"; 172 | _this2.cropImage.style.width = _this2.scaleWidth + "px"; 173 | _this2.cropImage.style.height = _this2.scaleHeight + "px"; 174 | _this2.cropImage.style.userSelect = "none"; 175 | _this2.cropZoom.value = 100; 176 | 177 | _this2.cropCanvas(); 178 | if (!_this2.hasEvent) { 179 | var okHander = function okHander() { 180 | var data = _this2.canvas.toDataURL("image/jpeg", _this2.quality); 181 | _this2.success && _this2.success(data.substr(23)); 182 | _this2.layout.parentNode.removeChild(_this2.layout); 183 | }; 184 | 185 | _this2.cropZoom.addEventListener("input", function (e) { 186 | that.zoom(e); 187 | }); 188 | _this2.cropOk.addEventListener("click", okHander); 189 | 190 | _this2.cropImage.addEventListener(that.isMobile ? "touchstart" : "mousedown", function (e) { 191 | e.preventDefault(); 192 | that.isDrag = true; 193 | that.beginX = e.pageX - e.target.offsetLeft; 194 | that.beginY = e.pageY - e.target.offsetTop; 195 | // 拖动鼠标 196 | var move = function move(e) { 197 | // console.log("move",e) 198 | // e.preventDefault(); 199 | 200 | if (!that.isDrag) return false; 201 | // 放大 202 | if (that.isMobile) { 203 | if (e.touches.length == 2) { 204 | that.scaleLength = that.touchData(e).length; 205 | that.scale = Math.min(200, Math.max(100, that.scaleLength / that.beginLength * 100)); 206 | this.zoom(); 207 | } else { 208 | that.scaleX = e.pageX - beginX; 209 | that.scaleY = e.pageY - beginY; 210 | } 211 | } else { 212 | that.scaleX = e.pageX - that.beginX; 213 | that.scaleY = e.pageY - that.beginY; 214 | } 215 | that.limit(); 216 | that.cropImage.style.width = that.scaleWidth + "px"; 217 | that.cropImage.style.height = that.scaleHeight + "px"; 218 | that.cropImage.style.left = that.scaleX + "px"; 219 | that.cropImage.style.top = that.scaleY + "px"; 220 | }; 221 | 222 | // 放开那个鼠标 223 | var end = function end(e) { 224 | // e.preventDefault(); 225 | that.isDrag = false; 226 | if (that.isMobile) { 227 | document.removeEventListener('touchmove', move, false); 228 | document.removeEventListener('touchend', end, false); 229 | } else { 230 | document.removeEventListener('mousemove', move, false); 231 | document.removeEventListener('mouseup', end, false); 232 | } 233 | }; 234 | if (that.isMobile) { 235 | if (e.touches.length == 2) { 236 | that.beginLength = that.beginLength == 0 ? that.touchData(e).length : that.beginLength; 237 | } 238 | document.addEventListener('touchmove', move); 239 | document.addEventListener('touchend', end); 240 | } else { 241 | document.addEventListener('mousemove', move); 242 | document.addEventListener('mouseup', end); 243 | } 244 | }); 245 | _this2.hasEvent = true; 246 | } 247 | }; 248 | this.img.src = url; 249 | } 250 | // 粘贴到canvas 251 | 252 | }, { 253 | key: "cropCanvas", 254 | value: function cropCanvas() { 255 | this.context.clearRect(0, 0, this.width, this.height); 256 | var sx = Math.round(Math.abs(this.scaleX) * this.img.naturalWidth / this.scaleWidth); //图像源x坐标 257 | var sy = Math.round(Math.abs(this.scaleY) * this.img.naturalHeight / this.scaleHeight); //图像源y坐标 258 | var sw = this.width * this.img.naturalWidth / this.scaleWidth >> 0; 259 | var sh = this.height * this.img.naturalHeight / this.scaleHeight >> 0; 260 | this.context.drawImage(this.img, sx, sy, sw, sh, 0, 0, this.width, this.height / this.cropRatio()); 261 | } 262 | 263 | // http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios 264 | // 修正 iPhone 上传的方向问题 265 | 266 | }, { 267 | key: "cropRatio", 268 | value: function cropRatio() { 269 | var iw = this.img.naturalWidth, 270 | ih = this.img.naturalHeight; 271 | var canvas = document.createElement('canvas'); 272 | canvas.width = 1; 273 | canvas.height = ih; 274 | var ctx = canvas.getContext('2d'); 275 | ctx.drawImage(this.img, 0, 0); 276 | var data = ctx.getImageData(0, 0, 1, ih).data; 277 | // search image edge pixel position in case it is squashed vertically. 278 | var sy = 0; 279 | var ey = ih; 280 | var py = ih; 281 | while (py > sy) { 282 | var alpha = data[(py - 1) * 4 + 3]; 283 | if (alpha === 0) { 284 | ey = py; 285 | } else { 286 | sy = py; 287 | } 288 | py = ey + sy >> 1; 289 | } 290 | var ratio = py / ih; 291 | return ratio === 0 ? 1 : ratio; 292 | } 293 | }, { 294 | key: "getOrientation", 295 | value: function getOrientation(file, callback) { 296 | var reader = new FileReader(); 297 | reader.onload = function (e) { 298 | 299 | var view = new DataView(e.target.result); 300 | if (view.getUint16(0, false) != 0xFFD8) return callback(-2); 301 | var length = view.byteLength, 302 | offset = 2; 303 | while (offset < length) { 304 | var marker = view.getUint16(offset, false); 305 | offset += 2; 306 | if (marker == 0xFFE1) { 307 | if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1); 308 | var little = view.getUint16(offset += 6, false) == 0x4949; 309 | offset += view.getUint32(offset + 4, little); 310 | var tags = view.getUint16(offset, little); 311 | offset += 2; 312 | for (var i = 0; i < tags; i++) { 313 | if (view.getUint16(offset + i * 12, little) == 0x0112) return callback(view.getUint16(offset + i * 12 + 8, little)); 314 | } 315 | } else if ((marker & 0xFF00) != 0xFF00) break;else offset += view.getUint16(offset, false); 316 | } 317 | return callback(-1); 318 | }; 319 | reader.readAsArrayBuffer(file.slice(0, 64 * 1024)); 320 | } 321 | 322 | //图片压缩 323 | 324 | }, { 325 | key: "fixedOrientation", 326 | value: function fixedOrientation(img, width, height, callback) { 327 | var canvas, ctx; 328 | canvas = document.createElement('canvas'); 329 | canvas.width = width; 330 | canvas.height = height; 331 | ctx = canvas.getContext("2d"); 332 | //如果图片方向等于6 ,则旋转矫正,反之则不做处理 333 | if (this.orientation == 6) { 334 | ctx.save(); 335 | ctx.translate(width / 2, height / 2); 336 | ctx.rotate(90 * Math.PI / 180); 337 | ctx.drawImage(img, 0 - height / 2, 0 - width / 2, height, width); 338 | ctx.restore(); 339 | } else { 340 | ctx.drawImage(img, 0, 0, width, height); 341 | } 342 | canvas.toBlob(function (blob) { 343 | var url = URL.createObjectURL(blob); 344 | callback && callback(url); 345 | }); 346 | } 347 | // 放大缩小 348 | 349 | }, { 350 | key: "zoom", 351 | value: function zoom(e) { 352 | var s = parseInt(e.target.value) / 100; 353 | var osx = this.scaleX - this.width / 2; 354 | var osy = this.scaleY - this.height / 2; 355 | var os = void 0; 356 | // 按原始图片比例优先使用小值设置最低值 357 | if (this.temp.width > this.temp.height) { 358 | os = this.scaleHeight / this.height; 359 | this.scaleHeight = this.height * s; 360 | this.scaleWidth = this.scaleHeight * this.temp.width / this.temp.height; 361 | } else { 362 | os = this.scaleWidth / this.width; 363 | this.scaleWidth = this.width * s; 364 | this.scaleHeight = this.scaleWidth * this.temp.height / this.temp.width; 365 | } 366 | this.scaleX = osx * s / os + this.width / 2; 367 | this.scaleY = osy * s / os + this.height / 2; 368 | this.limit(); 369 | this.cropCanvas(); 370 | this.cropImage.style.width = this.scaleWidth + "px"; 371 | this.cropImage.style.height = this.scaleHeight + "px"; 372 | this.cropImage.style.left = this.scaleX + "px"; 373 | this.cropImage.style.top = this.scaleY + "px"; 374 | } 375 | // 限制拖动检测边缘 376 | 377 | }, { 378 | key: "limit", 379 | value: function limit() { 380 | if (this.scaleX < -(this.scaleWidth - this.width)) this.scaleX = -(this.scaleWidth - this.width); 381 | if (this.scaleY < -(this.scaleHeight - this.height)) this.scaleY = -(this.scaleHeight - this.height); 382 | if (this.scaleX > 0) this.scaleX = 0; 383 | if (this.scaleY > 0) this.scaleY = 0; 384 | if (this.scaleHeight === this.height) this.scaleY = 0; 385 | if (this.scaleWidth === this.width) this.scaleX = 0; 386 | } 387 | 388 | // 获取多点触控 389 | 390 | }, { 391 | key: "touchData", 392 | value: function touchData(e) { 393 | if (e.touches.length < 2) return; 394 | var x1 = e.touches[0].pageX; 395 | var x2 = e.touches[1].pageX; 396 | var x3 = x1 <= x2 ? (x2 - x1) / 2 + x1 : (x1 - x2) / 2 + x2; 397 | var y1 = e.touches[0].pageY - this.scrollbar.scrollTop; 398 | var y2 = e.touches[1].pageY - this.scrollbar.scrollTop; 399 | var y3 = y1 <= y2 ? (y2 - y1) / 2 + y1 : (y1 - y2) / 2 + y2; 400 | return { 401 | length: Math.round(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))), 402 | x: Math.round(x3), 403 | y: Math.round(y3) 404 | }; 405 | } 406 | }, { 407 | key: "dataURLtoBlob", 408 | value: function dataURLtoBlob(dataurl) { 409 | var arr = dataurl.split(','); 410 | var mime = arr[0].match(/:(.*?);/)[1]; 411 | var bstr = atob(arr[1]); 412 | var n = bstr.length, 413 | u8arr = new Uint8Array(n); 414 | while (n--) { 415 | u8arr[n] = bstr.charCodeAt(n); 416 | } 417 | return URL.createObjectURL(new Blob([u8arr], { type: mime })); 418 | } 419 | }]); 420 | return crop; 421 | }(); 422 | 423 | return crop; 424 | 425 | }))); 426 | --------------------------------------------------------------------------------