Fork me on GitHub
image frame

Walter

面朝大海 春暖花开

解决多图片压缩上传和iOS端图片上传选择90度的问题

逻辑思路:
1、监听input文件值的change事件
2、获取文件列表
3、使用FileReader()函数读取图片信息,并转为base64格式
4、根据预设的阀值,判断是否压缩
5、如果不压缩,将图片转为blob格式,使用FormData()函数,上传图片
6、如果压缩图片,使用canvas重新绘制图片,根据需要,选择是否使用瓦片canvas
7、使用Exif.js,获取图片方向信息,确定是否需要使用canvas的rotate()函数进行旋转图片
8、上传图片

自定义图片上传组件:

//推荐
<input type='file' id='upload' accept='image/gif,image/jpeg,image/png,image/jpg' @change='upload' >
//不推荐使用下面这种方法,该形式在Chrome浏览器中,选择文件弹框会滞留一段时间才弹出
<input type='file' name='file' class='element' accept='image/*'>

引入exif.js

import Exif from 'exif-js'

具体实现

upload(e) {
    let files = e.target.files || e.dataTransfer.files;
    if (!files.length) return;
    this.picValue = files[0];
    this.imgPreview(this.picValue);
},

//图片预览、处理、上传
imgPreview(file) {
    let self = this;
    let Orientation;
    //去获取拍照时的信息,解决拍出来的照片旋转问题
    Exif.getData(file, function() {
        Orientation = Exif.getTag(this, 'Orientation');
    });
    // alert(Orientation,'console.log(Orientation)');
    debugger
    // 看支持不支持FileReader
    if (!file || !window.FileReader) return;

    // alert(file);
    // 创建一个reader
    let reader = new FileReader();
    // 将图片2将转成 base64 格式
    reader.readAsDataURL(file);
    // 读取成功后的回调
    reader.onloadend = function() {

        let result = this.result;
        let img = new Image();

        img.onload = function() {
            let data = self.compress(img, Orientation);
            /*self.headerImage = data;  */
            var blob = self.dataURItoBlob(data);
            var fd = new FormData();
            fd.append("file", blob, 'image.png');
            fd.fileName = blob;
            console.log(fd, 'fd')
            axios.post('/sunrise-gateway/oss/ossUpload', fd).then((res) => {
                return res
            }).then((res) => {
                console.log(res, 'res')
                if (res.data.code == '000000') {
                    self.list.push(...res.data.data);
                    self.objUrl = res.data.data[0];
                    self.picNum++;
                } else {
                    MessageBox.alert('上传失败0');
                }
                Indicator.close();
            }).catch((err) => {
                Indicator.close();
                MessageBox.alert('上传失败1');
            })
            // alert(blob)
        }
        img.src = result;
    }
},

//base64转blob
dataURItoBlob(base64Data) {
    var byteString;
    if (base64Data.split(',')[0].indexOf('base64') >= 0) {
        byteString = atob(base64Data.split(',')[1]);
    } else {
        byteString = unescape(base64Data.split(',')[1]);
    }
    var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], {
        type: mimeString
    });
},

//图片旋转
rotateImg(img, direction, canvas) {
    //最小与最大旋转方向,图片旋转4次后回到原方向
    const min_step = 0;
    const max_step = 3;
    if (img == null) return;
    //img的高度和宽度不能在img元素隐藏后获取,否则会出错
    let height = img.height;
    let width = img.width;
    let step = 2;
    if (step == null) {
        step = min_step;
    }
    if (direction == 'right') {
        step++;
        //旋转到原位置,即超过最大值
        step > max_step && (step = min_step);
    } else {
        step--;
        step < min_step && (step = max_step);
    }
    //旋转角度以弧度值为参数
    let degree = step * 90 * Math.PI / 180;
    let ctx = canvas.getContext('2d');
    switch (step) {
        case 0:
            canvas.width = width;
            canvas.height = height;
            ctx.drawImage(img, 0, 0);
            break;
        case 1:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, 0, -height);
            break;
        case 2:
            canvas.width = width;
            canvas.height = height;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, -height);
            break;
        case 3:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, 0);
            break;
    }
},

//图片压缩
compress(img, Orientation) {
    let canvas = document.createElement('canvas');
    let ctx = canvas.getContext('2d');
    //瓦片canvas
    let tCanvas = document.createElement('canvas');
    let tctx = tCanvas.getContext('2d');
    let initSize = img.src.length;
    let width = img.width;
    let height = img.height;
    //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
    let ratio;
    if ((ratio = width * height / 4000000) > 1) {
        // console.log('大于400万像素')
        ratio = Math.sqrt(ratio);
        width /= ratio;
        height /= ratio;
    } else {
        ratio = 1;
    }
    canvas.width = width;
    canvas.height = height;
    //铺底色
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    //如果图片像素大于100万则使用瓦片绘制
    let count;
    if ((count = width * height / 1000000) > 1) {
        // console.log('超过100W像素');
        count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片
        //计算每块瓦片的宽和高
        let nw = ~~(width / count);
        let nh = ~~(height / count);
        tCanvas.width = nw;
        tCanvas.height = nh;
        for (let i = 0; i < count; i++) {
            for (let j = 0; j < count; j++) {
                tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
                ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
            }
        }
    } else {
        ctx.drawImage(img, 0, 0, width, height);
    }
    //修复ios上传图片的时候 被旋转的问题
    if (Orientation != '' && Orientation != 1) {
        switch (Orientation) {
            case 6: //需要顺时针(向左)90度旋转
                this.rotateImg(img, 'left', canvas);
                break;
            case 8: //需要逆时针(向右)90度旋转
                this.rotateImg(img, 'right', canvas);
                break;
            case 3: //需要180度旋转
                this.rotateImg(img, 'right', canvas); //转两次
                this.rotateImg(img, 'right', canvas);
                break;
        }
    }
    //进行最小压缩    
    let ndata = canvas.toDataURL('image/jpeg', 0.1);
    tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
    return ndata;
},

备注:
canvas.toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 image/png 格式。图片的分辨率为96dpi。

在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

返回值:包含dataURI的DOMString。

DataURI格式:data:[][;base64],。其中mediatype声明了文件类型,遵循MIME规则,如“image/png”、“text/plain”;之后是编码类型,这里我们只涉及 base64;紧接着就是文件编码后的内容了。

URI(Uniform Resource Identifier):统一资源标识符,服务器资源名被称为统一资源标识符.如:img src='images/image.png'

URL(Uniform Resource Locator):统一资源定位符,描述了一台特定服务器上某资源的特定位置。如:

img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAWCAYAAAArdgcFAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsTAAALEwEAmpwYAAABSUlEQVQ4Ee1VPUsDQRB9s3dBRCRNQvCjkDQW/hJ/gb9AOwWDoIXFgYXYXGP+hYW1/gIr7WzsE8VGxZBcwt04dznCJWw2twtWutXMmzdvlrd7t4SZVbvi1ZihZuCFqT9A/B7Qd5FI44RpPcQpE1oC1IsEq5jRBeGyc0zXaV8mvhbyOREurIRMZMZhp0VthYB9mXBi4jrUztIev1bNbKg6CMxvIWwg5GWVDOHNZ7lX6l/wrG+Fzbh/ca1bf9QWBhKtHznobgtjKF92ZBL3TUWpRbK7LhiVCY8kY3xK/iTiexNcEywSXxKBrfHvrdBN2JRsp4BoQ3dbtHLT4K+JqxWw8vr4YDaf+vR+SmXRWw99lT9N96VaypIYtwgoyWyJRtiX3T+X7TXxROcxGeAo5chlyNcBVxrb2JVpTUHsz4IQi/DL6wPucENxqvoDx69PXP8OKn4AAAAASUVORK5CYII='/

mac开启本地服务器

Nginx

是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。

安装:
搜索软件:brew search nginx
安装软件:brew install nginx
卸载软件:brew uninstall nginx
升级brew:sudo brew update
查看安装信息:sudo brew info nginx
查看已经安装的软件:brew list

nginx常用命令:

nginx -v //查看下安装好的nginx版本
sudo nginx  // 启动nginx
sudo nginx              //启动nginx
sudo nginx -s reload    //修改配置后重新加载生效

#简单解释下,上面这种方法重启,nginx在重启的时候不会中断服务,因为  nginx在启动后,会有一个master进程和多个worker进程,重启是会先生成新的worker进程去接受reload命令,等老的worker进程执行完毕,master进程在关闭他们,所以服务器不会中断。

sudo nginx -s  reopen   //重新打开日志文件
sudo nginx -s stop  //快速停止nginx
sudo nginx -s quit  //完整有序的停止nginx
sudo nginx -t     //测试当前配置文件是否正确

nginx常见目录:

/usr/local/Cellar/  #nginx默认安装的目录,所有使用brew安装的软件默认都在这里
/usr/local/Cellar/nginx/1.8.0/html  #默认的访问目录,就是我们说的网站根目录
/usr/local/etc/nginx/     # nginx.conf所在的目录

修改默认路径从nginx/html改成你自己放置代码的路径
vim /usr/local/etc/nginx/nginx.conf

接下来的任务就很明确了,把listen 80下面的两个 root html中的路径,改成我们自己放代码的文件夹。例如在桌面下面新建一个home的文件夹,再在目录下新建一个www的文件夹,那么路径就是:桌面:homewww,把task1.html放进去,然后修改配置修改完成后,点击Esc键·shift+: 输入:wq,保存修改。

Apache

打开终端,开启Apache:

//开启apache:  sudo apachectl start

//重启apache:  sudo apachectl restart

//关闭apache:  sudo apachectl stop

//查看apache版本 sudo apachectl -v

回车会提示输入密码,也就是你电脑的密码,http://127.0.0.1/测试一下。
点击Finder,然后Command+Shift+G,前往Apache服务器的文件路径(/Library/WebServer/Documents),在步骤1中只输入一个http://127.0.0.1其实默认打开的是index.html.en(html是一个网页文件),该文件的内容就是在步骤1中测试时浏览器所显示的内容

vue使用webpack优化构建流程

初衷:如果我们的 Vue 项目比较大.或者说项目中引入了许多第三方库,那么在执行 npm run build 构建项目的时候会极其的慢.比如我现在的项目就每次打包就要 83s。
下面提供一些方法用来提供优化打包速度:

resolve.modules

思路:webpack 的 resolve.modules 是用来配置模块库(即 node_modules)所在的位置。当 js 里出现 import ‘vue’ 这样不是相对、也不是绝对路径的写法时,它便会到 node_modules 目录下去找。在默认配置下,webpack 会采用向上递归搜索的方式去寻找。但通常项目目录里只有一个 node_modules,且是在项目根目录。为了减少搜索范围,可我们以直接写明 node_modules 的全路径。

打开 build/webpack.base.conf.js 文件,添加如下配置:
module.exports = {
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    modules: [
      resolve('src'),
      resolve('node_modules')
    ],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },

webpack-parallel-uglify-plugin

原理:webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。源码:

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
  ]
}

我们可以改用 webpack-parallel-uglify-plugin 插件,它可以并行运行 UglifyJS 插件,从而更加充分、合理的使用 CPU 资源,从而大大减少构建时间。
当webpack有多个JS文件需要输出和压缩时候,原来会使用UglifyJS去一个个压缩并且输出,但是ParallelUglifyPlugin插件则会开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。无非就是变成了并行处理该压缩了,并行处理多个子任务,效率会更高。

安装:npm i webpack-parallel-uglify-plugin
打开 build/webpack.prod.conf.js 文件,并作如下修改:
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// 删掉webpack提供的UglifyJS插件
    //new UglifyJsPlugin({
    //  uglifyOptions: {
    //    compress: {
    //      warnings: false
    //    }
    //  },
    //  sourceMap: config.build.productionSourceMap,
    //  parallel: true
    //}),
    // 增加 webpack-parallel-uglify-plugin来替换
    new ParallelUglifyPlugin({
      cacheDir: '.cache/',
      uglifyJS:{
        output: {
          comments: false
        },
        compress: {
          warnings: false,
          drop_debugger: true,
          drop_console: true
        }
      }
    }),

HappyPack

原理:由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以 Webpack 需要处理的事情只能一件一件地做,不能多件事一起做。而 HappyPack 的处理思路是:将原有的 webpack 对 loader 的执行过程,从单一进程的形式扩展多进程模式,从而加速代码构建。

安装:npm i happypack
打开 build/webpack.base.conf.js 文件,并作如下修改:
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        //把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
        loader: 'happypack/loader?id=happyBabel',
        include: [resolve('src')],
        //排除node_modules 目录下的文件
        exclude: /node_modules/
      },
    ]
  },
  plugins: [
    new HappyPack({
        //用id来标识 happypack处理那里类文件
      id: 'happyBabel',
      //如何处理  用法和loader 的配置一样
      loaders: [{
        loader: 'babel-loader?cacheDirectory=true',
      }],
      //共享进程池
      threadPool: happyThreadPool,
      //允许 HappyPack 输出日志
      verbose: true,
    })
  ]
}

DllPlugin 和 DllReferencePlugin

原理:我们的项目依赖中通常会引用大量的 npm 包,而这些包在正常的开发过程中并不会进行修改,但是在每一次构建过程中却需要反复的将其解析,而下面介绍的两个插件就是用来规避此类损耗的:
DllPlugin 插件:作用是预先编译一些模块。
DllReferencePlugin 插件:它的所用则是把这些预先编译好的模块引用起来。
注意:DllPlugin 必须要在 DllReferencePlugin 执行前先执行一次。

1、在 build 文件夹中新建 webpack.dll.conf.js 文件,内容如下(主要是配置下需要提前编译打包的库):
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    vendor: ['vue/dist/vue.common.js',
            'vue-router',
            'axios',
            'vuex',
            'element-ui']
  },
  output: {
    path: path.join(__dirname, '../static/js'),
    filename: '[name].dll.js',
    library: '[name]_library'       // vendor.dll.js中暴露出的全局变量名
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, '.', '[name]-manifest.json'),
      name: '[name]_library'
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
};

2、编辑 package.json 文件,添加一条编译命令:
    dll: webpack --config webpack.dll.conf.js
接着执行 npm run dll 命令来生成 vendor.dll.js。注意:如果之后这些需要预编译的库又有变动,则需再次执行 npm run dll 命令来重新生成 vendor.dll.js

3、index.html 这边将 vendor.dll.js 引入进来:
<script src=‘./static/js/vendor.dll.js’></script>

4、打开 build/webpack.base.conf.js 文件,编辑添加如下高亮配置,作用是通过 DLLReferencePlugin 来使用 DllPlugin 生成的 DLL Bundle。
const webpack = require('webpack');

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  //.....
  plugins: [
     // 添加DllReferencePlugin插件
     new webpack.DllReferencePlugin({
       context: path.resolve(__dirname, '..'),
       manifest: require('./vendor-manifest.json')
     }),
  ]
}