Linux 下 ThinkPHP 与 Swoole 搭配 ThinkPHP 性能对比测试

ThinkPHP 5.0.21

Server Software:        nginx
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /
Document Length:        39 bytes

Concurrency Level:      10
Time taken for tests:   9.064 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      218000 bytes
HTML transferred:       39000 bytes
Requests per second:    110.33 [#/sec] (mean)
Time per request:       90.637 [ms] (mean)
Time per request:       9.064 [ms] (mean, across all concurrent requests)
Transfer rate:          23.49 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:    15   90  37.8     87     217
Waiting:       15   90  37.8     87     217
Total:         15   90  37.8     88     217

Swoole 搭配 ThinkPHP 5.0.21

Server Software:        swoole-http-server
Server Hostname:        127.0.0.1
Server Port:            9501

Document Path:          /
Document Length:        39 bytes

Concurrency Level:      10
Time taken for tests:   4.355 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1870000 bytes
HTML transferred:       390000 bytes
Requests per second:    2296.38 [#/sec] (mean)
Time per request:       4.355 [ms] (mean)
Time per request:       0.435 [ms] (mean, across all concurrent requests)
Transfer rate:          419.36 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       6
Processing:     1    4   3.7      4     166
Waiting:        0    4   3.6      3     166
Total:          1    4   3.7      4     166

配置信息

安装 PHP memcache memcached

安装 memcached

yum install memcached

需要 libevent 库,如未装则运行如下

yum install libevent libevent-devel

安装 php memcache 扩展

PHP memcache

http://pecl.php.net/package/memcache

PHP memcached

http://pecl.php.net/package/memcached
wget http://pecl.php.net/get/memcache-3.0.8.tgz
tar zxvf memcache-3.0.8.tgz
cd memcache-3.0.8
/usr/local/php5/bin/phpize
./configure --with-php-config=/usr/local/php5/bin/php-config
make
make install

php.ini 中加入扩展 extension=memcache.so

vim /usr/local/php5/lib/php.ini

重启 php-fpm,查看 phpinfo 

开机启动 (更改端口号如 11811)(如果使用 yum 方式安装,默认自启动)

vim /etc/sysconfig/memcached
PORT="11811"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

编译方式安装的,在 /etc/rc.d/rc.local 中加入 (先 which memcached)

/usr/local/bin/memcached -u www -d -p 11811 -P /tmp/memcached.pid

或者创建 /etc/init.d/memcached

#!/bin/sh 
# 
# memcached:    MemCached Daemon 
# 
# chkconfig:    - 90 25
# description:  MemCached Daemon 
# 
# Source function library.
. /etc/rc.d/init.d/functions 
. /etc/sysconfig/network 
#[ ${NETWORKING} = "no" ] && exit 0
#[ -r /etc/sysconfig/dund ] || exit 0
#. /etc/sysconfig/dund 
#[ -z "$DUNDARGS" ] && exit 0
MEMCACHED="/usr/bin/memcached"
SERVER_IP="127.0.0.1"
SERVER_PORT="11811"
[ -f $MEMCACHED ] || exit 1
start() 
{ 
        echo -n $"Starting memcached: "
        daemon $MEMCACHED -u daemon -d -m 2048 -l $SERVER_IP -p $SERVER_PORT -P /tmp/memcached.pid
        echo 
} 
stop() 
{ 
        echo -n $"Shutting down memcached: "
        killproc memcached 
        echo 
}
  
# See how we were called. 
case "$1" in 
  start) 
        start 
        ;; 
  stop) 
        stop 
        ;; 
  restart) 
        stop 
        sleep 3
        start 
        ;; 
    *) 
        echo $"Usage: $0 {start|stop|restart}"
        exit 1
esac 
exit 0
#  chmod 755 /etc/init.d/memcached
#  chkconfig --add memcached
#  chkconfig memcached on
#  service memcached start
#  chkconfig --list memcached  #查看是否设置成功

启动

memcached -u root -d -p 11811

微信小程序 写一个 多级通讯录

无限级通讯录

WXML

<view class='title-container'>
  <view class="title" wx:for="{{branchTitle}}" wx:key="key" wx:for-index="index" wx:for-item="item" bindtap='toPre' data-level="{{item.level}}">
    <block wx:if="{{index==0}}">
      <text>{{item.title}}</text>
    </block>
    <block wx:else>
      <image class='next-icon' mode='aspectFit' src='/resource/cal/next.png'></image>
      <text>{{item.title}}</text>
    </block>
  </view>
</view>
<view class='address-container'>
  <view class='address-list' wx:for="{{address}}" wx:key="keyL" wx:for-index="indexL" wx:for-item="itemL">
    <block wx:if="{{itemL.type == 'branch'}}">
      <view class='address-one' bindtap='toId' data-id="{{itemL.id}}">
        <view class='left' style='background-color:red'>
          <view>{{itemL.name_slice}}</view>
        </view>
        <view class='right'>
          <view class='name-container'>
            <text class='name'>{{itemL.name}}</text>
          </view>
        </view>
      </view>
    </block>
    <block wx:elif="{{itemL.type == 'people'}}">
      <view class='address-one'>
        <view class='left'>
          <view>{{itemL.name_slice}}</view>
        </view>
        <view class='right'>
          <view class='name-container'>
            <text class='name'>{{itemL.name}}</text>
            <text class='job'>{{itemL.job}}</text>
          </view>
          <view class='tel-container'>
            <text class='tel'>{{itemL.tel}}</text>
          </view>
        </view>
      </view>
    </block>
  </view>
</view>

JS

// pages/addressbook/addressbook.js
const util = require('../../utils/util.js')
Page({

  /**
   * 页面的初始数据
   */
  data: {
    address: [],
    branchTitle: [{
      'level': 0,
      'title': '总部'
    }],
    level: 0,
  },

  toId: function (e) {
    console.log(e)
    var id = e.currentTarget.dataset.id

    var address = this.id2Detail(id)
    var title = this.id2Title(id)

    var oldBranchTitle = this.data.branchTitle
    var length = oldBranchTitle.length

    var branchTitle = oldBranchTitle
    branchTitle = branchTitle.concat([{
      'level': length + 1,
      'title': title
    }])

    var obj = {
      'address': JSON.stringify(address),
      'branchTitle': JSON.stringify(branchTitle)
    }

    var str = util.obj2Query(obj)
    wx.navigateTo({
      url: '/pages/addressbook/addressbook?' + str,
    })
    console.log(address)
  },
  toPre: function (e) {
    var level = e.currentTarget.dataset.level
    var currentLevel = this.data.level
    console.log(currentLevel)
    console.log('currentLevel - level = ' + currentLevel + '-' + level + '=' + (currentLevel - level))
    if (currentLevel - level != 0) {
      wx.navigateBack({
        delta: currentLevel - level
      })
    }

  },
  addressFormat: function (address = []) {
    var i
    for (i in address) {
      if (address[i].type == 'branch') {
        address[i]['name_slice'] = address[i].name.substr(0, 2)
      } else if (address[i].type == 'people') {
        address[i]['name_slice'] = address[i].name.substr(address[i].name.length - 2, 2)
      }
    }
    return address
  },
  id2Title: function (id) {
    var address = this.data.address
    var i
    for (i in address) {
      if (address[i].id == id) {
        return address[i].name
      }
    }
  },

  id2Detail: function (id) {
    var address = this.data.address
    var i
    for (i in address) {
      if (address[i].id == id) {
        return address[i].subclass
      }
    }
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    console.log('onLoad')
    console.log(options)
    if (options.hasOwnProperty('address')) {
      var address = JSON.parse(options.address)
      var branchTitle = JSON.parse(options.branchTitle)
      var level = branchTitle[branchTitle.length - 1].level
      address = this.addressFormat(address)
      this.setData({
        address: address,
        branchTitle: branchTitle,
        level: level
      })
    } else {
      this.init()
    }
  },
  init: function () {
    var address = [{
      'id': 1,
      'name': '总经办',
      'type': 'branch',
      'subclass': [{
        'id': 2,
        'type': 'people',
        'name': '张震岳',
        'job': '法务',
        'tel': '18888888888'
      },
      {
        'id': 3,
        'type': 'people',
        'name': '李宇春',
        'job': '法务',
        'tel': '18888888868'
      },
      ]
    },
    {
      'id': 4,
      'name': '人事行政部',
      'type': 'branch',
      'subclass': [{
        'id': 5,
        'type': 'people',
        'name': '佐助',
        'job': '人事总监',
        'tel': '1883333668'
      },
      {
        'id': 6,
        'type': 'people',
        'name': '鸣人',
        'job': '招聘主管',
        'tel': '1666688888'
      }
      ]
    },
    {
      'id': 8,
      'name': '运营部',
      'type': 'branch',
      'subclass': [{
        'id': 5,
        'type': 'people',
        'name': '易烊千玺',
        'job': '运营总监',
        'tel': '1883333668'
      },
      {
        'id': 6,
        'type': 'people',
        'name': '王源',
        'job': '总监助理',
        'tel': '1666688888'
      },
      {
        'id': 8,
        'name': '华北区',
        'type': 'branch',
        'subclass': [{
          'id': 8,
          'name': '北京芍药居',
          'type': 'branch',
          'subclass': [{
            'id': 5,
            'type': 'people',
            'name': '爱德华',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '阿尔冯思',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 38,
          'name': '天津芥园道',
          'type': 'branch',
          'subclass': [{
            'id': 25,
            'type': 'people',
            'name': '若林源三',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '大空翼',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 5,
          'type': 'people',
          'name': '郑少秋',
          'job': '华北区总监',
          'tel': '1883333668'
        },
        {
          'id': 6,
          'type': 'people',
          'name': '刘德华',
          'job': '华北区总监助理',
          'tel': '1666688888'
        }
        ]
      },
      {
        'id': 12,
        'name': '西南区',
        'type': 'branch',
        'subclass': [{
          'id': 18,
          'name': '昆明汇都店',
          'type': 'branch',
          'subclass': [{
            'id': 5,
            'type': 'people',
            'name': '邓紫棋',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '汪峰',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 36,
          'name': '成都武侯祠',
          'type': 'branch',
          'subclass': [{
            'id': 5,
            'type': 'people',
            'name': '林俊杰',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '王菲',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 5,
          'type': 'people',
          'name': '萨琳娜',
          'job': '人事总监',
          'tel': '1883333668'
        },
        {
          'id': 6,
          'type': 'people',
          'name': '田馥甄',
          'job': '招聘主管',
          'tel': '1666688888'
        }
        ]
      },
      ]
    },
    {
      'id': 7,
      'type': 'people',
      'name': '孙总',
      'job': '董事长',
      'tel': '16666666666'
    }
    ]
    address = this.addressFormat(address)
    this.setData({
      address: address
    })
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    console.log('onshow')
    console.log(this.data.branchTitle)

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

WXSS

.title-container {
  padding: 30rpx 50rpx;
  font-size: 30rpx;
  background-color: #fefefe;
  box-shadow: 0 10rpx 10rpx #efefef;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}

.title {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}

.next-icon {
  width: 20rpx;
  height: 20rpx;
  margin: 0 10rpx;
}

.address-container {
  margin-top: 20rpx;
  background-color: #fefefe;
}

.address-list {
  padding: 30rpx 50rpx;
}

.address-one {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}

.address-one .left {
  width: 90rpx;
  height: 90rpx;
  border-radius: 50%;
  background-color: #2396f2;
  color: #fff;
  font-size: 30rpx;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  margin-right: 20rpx;
}

.address-one .right {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  font-size: 30rpx;
}

.name {
  font-weight: 700;
  margin-right: 20rpx;
}

.job {
  color: #aaa;
  font-size: 26rpx;
}

.tel {
  color: #aaa;
  font-size: 26rpx;
}

.swiper-tab {
  width: 100%;
  border-bottom: 2rpx solid #ccc;
  text-align: center;
  height: 88rpx;
  line-height: 88rpx;
  font-weight: bold;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
}

.swiper-tab-item {
  display: inline-block;
  /* width: 33.33%; */
  width: inherit;
  color: red;
}

.active {
  color: aqua;
  border-bottom: 4rpx solid red;
}

util.js

const obj2Query = function (params = new Object()) {
  console.log('obj2Query 方法')
  var arr = Object.keys(params)
  if (arr.length == 0) {
    return ''
  }
  var str = ''
  for (var i in params) {
    str = str + '&' + i + '=' + params[i]
  }
  return str
}

module.exports = {
  obj2Query: obj2Query
}

MacOS 自带 PHP 执行 php-fpm 的问题及解决

MacOS 下执行 php-fpm 会报错

ERROR: failed to open configuration file '/private/etc/php-fpm.conf': No such file or directory (2)
ERROR: failed to load configuration file '/private/etc/php-fpm.conf'
ERROR: FPM initialization failed

不能打开配置文件,因为没有 php-fpm.conf 文件,复制 php-fpm.conf.default 文件,改名为 php-fpm.conf,然后再根据需要改动配置。

cp /private/etc/php-fpm.conf.default /private/etc/php-fpm.conf

在此执行 php-fpm

WARNING: Nothing matches the include pattern '/private/etc/php-fpm.d/*.conf' from /private/etc/php-fpm.conf at line 125.
ERROR: failed to open error_log (/usr/var/log/php-fpm.log): No such file or directory (2)
ERROR: failed to post process the configuration
ERROR: FPM initialization failed

WARNING 找不到配置文件夹

cd /private/etc/php-fpm.d 
sudo cp www.conf.default www.conf

ERROR 不能打开错误日志文件。因为没有这个目录,配置到 /usr/local/var/log 目录。

vim /private/etc/php-fpm.conf,将 error_log 配置为 /usr/local/var/log/php-fpm.log

再次执行 php-fpm 依然报错

NOTICE: [pool www] 'user' directive is ignored when FPM is not running as root
NOTICE: [pool www] 'group' directive is ignored when FPM is not running as root

sudo php-fpm,再次报错:

ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (48)
ERROR: FPM initialization failed

编辑 www.conf,修改 listen 为 127.0.0.1:9999。

sudo vim /private/etc/php-fpm.d/www.conf

最后再次开启

开启php-fpm: sudo php-fpm -D

nginx 使用 php-fpm

nginx 配置文件

vim /usr/local/etc/nginx/nginx.conf
server {
    listen       8080;
    server_name  localhost;

    location / {
        root   /Users/guofeng/wwwroot/thinkphp/public;
        index  index.html index.htm index.php;
    }
    location ~ \.php$ {
        root /Users/guofeng/wwwroot/thinkphp/public;
        fastcgi_pass   127.0.0.1:9999;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

Swoole 适配 ThinkPHP 5 ab 压力测试 与 原生 ThinkPHP 对比

swoole 适配 thinkphp 5.0.21

Server Software:        swoole-http-server
Server Hostname:        127.0.0.1
Server Port:            9501

Document Path:          /
Document Length:        39 bytes

Concurrency Level:      10
Time taken for tests:   2.378 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1870000 bytes
HTML transferred:       390000 bytes
Requests per second:    4204.38 [#/sec] (mean)
Time per request:       2.378 [ms] (mean)
Time per request:       0.238 [ms] (mean, across all concurrent requests)
Transfer rate:          767.79 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:     1    2   1.2      2      21
Waiting:        1    2   1.1      2      21
Total:          1    2   1.1      2      21

与原生 ThinkPHP 对比

Server Software:        nginx/1.15.3
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /index.php
Document Length:        39 bytes

Concurrency Level:      10
Time taken for tests:   48.468 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      2020000 bytes
HTML transferred:       390000 bytes
Requests per second:    206.32 [#/sec] (mean)
Time per request:       48.468 [ms] (mean)
Time per request:       4.847 [ms] (mean, across all concurrent requests)
Transfer rate:          40.70 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2  75.8      0    3611
Processing:     9   45  73.4     42    3651
Waiting:        9   45  73.4     42    3651
Total:          9   47 105.2     42    3651

原生 PHP 输出 hello world

Server Software:        nginx/1.15.3
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /hello_world.php
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.870 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      870000 bytes
HTML transferred:       55000 bytes
Requests per second:    5749.02 [#/sec] (mean)
Time per request:       1.739 [ms] (mean)
Time per request:       0.174 [ms] (mean, across all concurrent requests)
Transfer rate:          976.88 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:     1    2   0.5      1       5
Waiting:        1    1   0.4      1       4
Total:          1    2   0.5      2       5

本机配置

Swoole 适配 ThinkPHP 5.0.21

新增代码

server/http_server.php

<?php
/**
 * Created by PhpStorm.
 * User: guofeng
 * Date: 2018/9/8
 * Time: 上午11:21
 */

$http = new swoole_http_server('0.0.0.0',9501);
$http->set([
    'enable_static_handler'=>true,
    'document_root'=>'../public/static',
    'worker_num'=>4,
]);
$http->on('WorkerStart',function(swoole_server $server,$worker_id){
    // 定义应用目录
    define('APP_PATH', __DIR__ . '/../application/');
    // ThinkPHP 引导文件
    // 1. 加载基础文件
    require __DIR__ . '/../thinkphp/base.php';
});
$http->on('request',function ($request,$response) use($http){

    $_SERVER = [];
    if(isset($request->server)){
        foreach($request->server as $k => $v){
            $_SERVER[strtoupper($k)] = $v;
        }
    }

    if(isset($request->header)){
        foreach($request->header as $k => $v){
            $_SERVER[strtoupper($k)] = $v;
        }
    }

    $_GET = [];
    if(isset($request->get)){
        foreach($request->get as $k => $v){
            $_GET[$k] = $v;
        }
    }

    $_POST = [];
    if(isset($request->post)){
        foreach($request->post as $k => $v){
            $_POST[$k] = $v;
        }
    }

    // 2. 执行应用
    ob_start();

    try{
        think\App::run()->send();
    }catch (\Exception $e){
        //todo
    }

    $res = ob_get_contents();
    ob_end_clean();
    $response->end($res);
});
$http->start();

ThinkPHP 核心文件修改

thinkphp/library/think/Request.php

/**
 * 获取当前请求URL的pathinfo信息(含URL后缀)
 * @access public
 * @return string
 */
public function pathinfo()
{
    if(isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='/'){
        return ltrim($_SERVER['PATH_INFO'], '/');
    }
    //if (is_null($this->pathinfo)) {
        if (isset($_GET[Config::get('var_pathinfo')])) {
            // 判断URL里面是否有兼容模式参数
            $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
            unset($_GET[Config::get('var_pathinfo')]);
        } elseif (IS_CLI) {
            // CLI模式下 index.php module/controller/action/params/...
            $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
        }

        // 分析PATHINFO信息
        if (!isset($_SERVER['PATH_INFO'])) {
            foreach (Config::get('pathinfo_fetch') as $type) {
                if (!empty($_SERVER[$type])) {
                    $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
                    substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
                    break;
                }
            }
        }
        $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
    //}
    return $this->pathinfo;
}
/**
 * 获取当前请求URL的pathinfo信息(不含URL后缀)
 * @access public
 * @return string
 */
public function path()
{
    //if (is_null($this->path)) {
        $suffix   = Config::get('url_html_suffix');
        $pathinfo = $this->pathinfo();
        if (false === $suffix) {
            // 禁止伪静态访问
            $this->path = $pathinfo;
        } elseif ($suffix) {
            // 去除正常的URL后缀
            $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
        } else {
            // 允许任何后缀访问
            $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
        }
    //}
    return $this->path;
}

访问方式

命令行 启动

php http_server.php

访问地址

http://localhost:9501/?s=index/index/index
http://localhost:9501/index/index/index

JS 两个数组合并

//concat()把两个或者多个数组链接在一起,但是不改变已经存在的数组
//而是返回一个链接之后的新数组
var a = [1,2,3];
a.concat([4,5]);
console.log(a);
//此处输出为 [1, 2, 3]

var a = [1,2,3];
a = a.concat([4,5]);
console.log(a);
//此处输出为 [1, 2, 3 ,4 ,5]

Web 生成图片 html2canvas 示例

http://blog.liuguofeng.com/wp-content/uploads/2018/09/html2canvas.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Using an existing canvas to draw on</title>
    <style>	
        button {
            clear: both;
            display: block;
        }
    </style>
</head>
<body>
<div><h1>这是HTML</h1>
	
    <div id="content" style="width:200px;height:200px;background: rgba(100, 255, 255, 0.5);">
	<img src='timg.jpg' style="width:100px;height:60px;">
	<span>这是文字</span>
	</div>
</div>
<h1>生成的图片</h1>
<canvas width="200" height="200"></canvas>
<script type="text/javascript" src="html2canvas.js"></script>
<button>点击生成图片</button>
<script type="text/javascript">
    var canvas = document.querySelector("canvas");
    document.querySelector("button").addEventListener("click", function() {
        html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) {
            console.log('Drew on the existing canvas');
        });
    }, false);
</script>
</body>
</html>