Giter Club home page Giter Club logo

Comments (36)

Louiszhai avatar Louiszhai commented on July 16, 2024

demo运行有问题吗,如果没问题,坐标问题看看页面中canvas所在父容器css样式对不对。
ios无法签名是个什么情况?有稳定的复现步骤吗,操作系统版本呢?

from canvas-draw.

LCtenacity avatar LCtenacity commented on July 16, 2024

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

图1234对应的是css样式吗?你贴的是父容器的css样式吗?如果是,那我觉得这些css与问题无关。

是这样的,如果确认demo在你的手机上运行正常,而你写的代码部分ios手机无法签名(需要刷新),建议耐心debug下canvas画布的touchstart、touchmove事件,看看有无异常。或者留下你qq,我加你好友

from canvas-draw.

yuemeimei avatar yuemeimei commented on July 16, 2024

en能动态的修改canvas的背景图吗?

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai @LCtenacity
你好,我也遇到这个问题,只有刷新画布才能签名,你们有解决的方法吗?
谢谢!

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai
直接复制的demo代码如下:

// 页面Page1.vue
 <div>
    <div id="canvasBox" :style="getHorizontalStyle">
      <div class="greet">
        <span>{{msg}}</span>
        <input type="button" value="清屏" @touchstart="clear" @mousedown="clear"/>
        <!-- <input type="button" value="生成png图片" @touchstart="savePNG" @mousedown="savePNG"/> -->
      </div>
      <canvas></canvas>
    </div>
    <!-- <div class="image-box" v-show="showBox">
      <header>
        请长按图片并保存至本地后发送好友
        <input type="button" value="返回" @click="showBox = false"/>
      </header>
      <img :src="signImage">
    </div> -->
  </div>

需要跳转到Page1.vue的代码:

this.$router.push({name: 'page1'})

类似上面的代码,当采用上面的代码跳转到Page1.vue的时候,在画布上移动指针,发现画布没有任何变化,没有画上,然后刷新Page1对应的页面,重新移动指针,发现画布上可以画上对应的签名。

debug对应的draw.js发现touchmove触发了啊!
test3

另外发现,这两个进入页面的方式唯一的不同是,如果是直接刷新页面进去的,发现console里面有这一段日志:

vue.esm.js:591 [Vue warn]: Do not use built-in or reserved HTML elements as component id: canvas

,如果是直接通过this.$router.push({name: 'page1'})进去的就不会出现上面的log, 然后画布就不能画签名,奇怪啊!

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

什么情况下需要刷新画布,麻烦提供尽可能详细的背景信息 Walter Hu [email protected]于2018年11月29日 周四21:18写道:

@Louiszhai https://github.com/Louiszhai @LCtenacity https://github.com/LCtenacity 你好,我也遇到这个问题,只有刷新画布才能签名,你们有解决的方法吗? 谢谢! — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#5 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/AIBcjeXYgGuUd4_4jPYT6V3yNUZAci0kks5uz96jgaJpZM4T6Uqs .

刷新画布是因为发现直接通过this.$router进去没有发现,只能刷新下页面,发现此时画布可以正常签名了。
@Louiszhai 你好,麻烦能帮忙看下吗?我感觉大部分都会遇到这个问题,我的QQ是: [email protected], 谢谢了!

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

跟你的demo的代码一样的,我贴下所有的代码:
唯一的不同的地方就是将你的draw.js 采用eslint格式化了,感觉是组件那里加载不对导致的,可是也没有发现哪里加载出问题了?只有刷新这个页面,画布上才能签名。

vue版本是: 2.5.2
<template>
  <div class="container">
    <div id="canvasBox" :style="getHorizontalStyle" v-show="!showBox">
      <div class="greet">
        <span>{{msg}}</span>
        <input type="button" value="清屏" @touchstart="clear" @mousedown="clear"/>
        <input type="button" value="生成png图片" @touchstart="savePNG" @mousedown="savePNG"/>
      </div>
      <canvas></canvas>
    </div>
    <div class="image-box" v-show="showBox">
      <header>
        请长按图片并保存至本地后发送好友
        <input type="button" value="返回" @click="showBox = false"/>
      </header>
      <img :src="signImage">
    </div>
  </div>
</template>

<script>
import Draw from '../components/draw'

export default {
  name: 'canvas',
  data () {
    return {
      msg: '请在下方空白处签名',
      degree: 90,
      signImage: null,
      showBox: false
    }
  },
  components: {
    Draw
  },
  beforeCreate () {
    document.title = '手写签名'
  },
  mounted () {
    this.$store.dispatch('UpdateShowHeader', true)
    this.$store.dispatch('UpdateShowTabbar', false)
    this.canvasBox = document.getElementById('canvasBox')
    this.initCanvas()
  },
  computed: {
    getHorizontalStyle () {
      const d = document
      const w = window.innerWidth || d.documentElement.clientWidth || d.body.clientWidth
      const h = window.innerHeight || d.documentElement.clientHeight || d.body.clientHeight
      let length = (h - w) / 2
      let width = w
      let height = h

      switch (this.degree) {
        case -90:
          length = -length
        /* eslint no-fallthrough: "error" */
        case 90:
          width = h
          height = w
          break
        default:
          length = 0
      }
      if (this.canvasBox) {
        this.canvasBox.removeChild(document.querySelector('canvas'))
        this.canvasBox.appendChild(document.createElement('canvas'))
        /* eslint  vue/no-async-in-computed-properties: "off" */
        setTimeout(() => {
          this.initCanvas()
        }, 200)
      }
      return {
        transform: `rotate(${this.degree}deg) translate(${length}px,${length}px)`,
        width: `${width}px`,
        height: `${height}px`,
        transformOrigin: 'center center'
      }
    }
  },
  methods: {
    initCanvas () {
      const canvas = document.querySelector('canvas')
      this.draw = new Draw(canvas, -this.degree)
    },
    clear () {
      this.draw.clear()
    },
    download () {
      this.draw.downloadPNGImage(this.draw.getPNGImage())
    },
    savePNG () {
      this.signImage = this.draw.getPNGImage()
      this.showBox = true
    },
    upload () {
      const image = this.draw.getPNGImage()
      const blob = this.draw.dataURLtoBlob(image)

      const url = ''
      const successCallback = (response) => {
        console.log(response)
      }
      const failureCallback = (error) => {
        console.log(error)
      }
      this.draw.upload(blob, url, successCallback, failureCallback)
    }
  }
}

</script>

<style>
  .container {
    width: 100%;
    height: 100%;
  }
  #canvasBox {
    display: flex;
    flex-direction: column;
    height: 100%;
  }
  .greet {
    padding: 20px;
    font-size: 20px;
    user-select: none;
  }
  input {
    font-size: 20px;
  }
  .greet select {
    font-size: 18px;
  }
  canvas {
    flex: 1;
    cursor: crosshair;
    border:2px dashed lightgray;
  }
  .image-box {
    width: 100%;
    height: 100%;
  }
  .image-box header{
    font-size: 18px;
  }
  .image-box img {
    max-width: 80%;
    max-height: 80%;
    margin-top: 50px;
    border: 1px solid gray;
  }
</style>


下面的是draw.js的代码:

/**
 * Created by louizhai on 17/6/30.
 * description: Use canvas to draw.
 */
function Draw (canvas, degree, config = {}) {
  if (!(this instanceof Draw)) {
    return new Draw(canvas, config)
  }
  if (!canvas) {
    return
  }
  let { width, height } = window.getComputedStyle(canvas, null)
  width = width.replace('px', '')
  height = height.replace('px', '')

  this.canvas = canvas
  this.context = canvas.getContext('2d')
  this.width = width
  this.height = height
  const context = this.context

  // 根据设备像素比优化canvas绘图
  const devicePixelRatio = window.devicePixelRatio
  if (devicePixelRatio) {
    canvas.style.width = `${width}px`
    canvas.style.height = `${height}px`
    canvas.height = height * devicePixelRatio
    canvas.width = width * devicePixelRatio
    context.scale(devicePixelRatio, devicePixelRatio)
  } else {
    canvas.width = width
    canvas.height = height
  }

  context.lineWidth = 6
  context.strokeStyle = 'black'
  context.lineCap = 'round'
  context.lineJoin = 'round'
  Object.assign(context, config)

  const { left, top } = canvas.getBoundingClientRect()
  const point = {}
  const isMobile = /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(navigator.userAgent)
  // 移动端性能太弱, 去掉模糊以提高手写渲染速度
  if (!isMobile) {
    context.shadowBlur = 1
    context.shadowColor = 'black'
  }
  let pressed = false

  const paint = (signal) => {
    switch (signal) {
      case 1:
        context.beginPath()
        context.moveTo(point.x, point.y)
      /* eslint no-fallthrough: "error" */
      case 2:
        context.lineTo(point.x, point.y)
        context.stroke()
        break
      default:
    }
  }
  const create = signal => (e) => {
    e.preventDefault()
    if (signal === 1) {
      pressed = true
    }
    if (signal === 1 || pressed) {
      e = isMobile ? e.touches[0] : e
      point.x = e.clientX - left
      point.y = e.clientY - top
      paint(signal)
    }
  }
  const start = create(1)
  const move = create(2)
  const requestAnimationFrame = window.requestAnimationFrame
  const optimizedMove = requestAnimationFrame ? (e) => {
    requestAnimationFrame(() => {
      move(e)
    })
  } : move

  if (isMobile) {
    canvas.addEventListener('touchstart', start)
    canvas.addEventListener('touchmove', optimizedMove)
  } else {
    canvas.addEventListener('mousedown', start)
    canvas.addEventListener('mousemove', optimizedMove);
    ['mouseup', 'mouseleave'].forEach((event) => {
      canvas.addEventListener(event, () => {
        pressed = false
      })
    })
  }

  // 重置画布坐标系
  if (typeof degree === 'number') {
    this.degree = degree
    context.rotate((degree * Math.PI) / 180)
    switch (degree) {
      case -90:
        context.translate(-height, 0)
        break
      case 90:
        context.translate(0, -width)
        break
      case -180:
      case 180:
        context.translate(-width, -height)
        break
      default:
    }
  }
}
Draw.prototype = {
  scale (width, height, canvas = this.canvas) {
    const w = canvas.width
    const h = canvas.height
    width = width || w
    height = height || h
    if (width !== w || height !== h) {
      const tmpCanvas = document.createElement('canvas')
      const tmpContext = tmpCanvas.getContext('2d')
      tmpCanvas.width = width
      tmpCanvas.height = height
      tmpContext.drawImage(canvas, 0, 0, w, h, 0, 0, width, height)
      canvas = tmpCanvas
    }
    return canvas
  },
  rotate (degree, image = this.canvas) {
    degree = ~~degree
    if (degree !== 0) {
      const maxDegree = 180
      const minDegree = -90
      if (degree > maxDegree) {
        degree = maxDegree
      } else if (degree < minDegree) {
        degree = minDegree
      }

      const canvas = document.createElement('canvas')
      const context = canvas.getContext('2d')
      const height = image.height
      const width = image.width
      const degreePI = (degree * Math.PI) / 180

      switch (degree) {
        // 逆时针旋转90°
        case -90:
          canvas.width = height
          canvas.height = width
          context.rotate(degreePI)
          context.drawImage(image, -width, 0)
          break
        // 顺时针旋转90°
        case 90:
          canvas.width = height
          canvas.height = width
          context.rotate(degreePI)
          context.drawImage(image, 0, -height)
          break
        // 顺时针旋转180°
        case 180:
          canvas.width = width
          canvas.height = height
          context.rotate(degreePI)
          context.drawImage(image, -width, -height)
          break
        default:
      }
      image = canvas
    }
    return image
  },
  getPNGImage (canvas = this.canvas) {
    return canvas.toDataURL('image/png')
  },
  getJPGImage (canvas = this.canvas) {
    return canvas.toDataURL('image/jpeg', 0.5)
  },
  downloadPNGImage (image) {
    const url = image.replace('image/png', 'image/octet-stream;Content-Disposition:attachment;filename=test.png')
    window.location.href = url
  },
  dataURLtoBlob (dataURL) {
    const arr = dataURL.split(',')
    const mime = arr[0].match(/:(.*?);/)[1]
    const bStr = atob(arr[1])
    let n = bStr.length
    const u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bStr.charCodeAt(n)
    }
    return new Blob([u8arr], { type: mime })
  },
  clear () {
    let width
    let height
    switch (this.degree) {
      case -90:
      case 90:
        width = this.height
        height = this.width
        break
      default:
        width = this.width
        height = this.height
    }
    this.context.clearRect(0, 0, width, height)
  },
  upload (blob, url, success, failure) {
    const formData = new FormData()
    const xhr = new XMLHttpRequest()
    xhr.withCredentials = true
    formData.append('image', blob, 'sign')

    xhr.open('POST', url, true)
    xhr.onload = () => {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        success(xhr.responseText)
      } else {
        failure()
      }
    }
    xhr.onerror = (e) => {
      if (typeof failure === 'function') {
        failure(e)
      } else {
        console.log(`upload img error: ${e}`)
      }
    }
    xhr.send(formData)
  }
}
export default Draw

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

感谢反馈,晚点我在iphone手机上测试下

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

请问你的手机是什么版本?

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

谢谢作者 @Louiszhai , 是直接在chrome的开发工具上测试的,没有手机测试,我等会给你手机测试的结果。

Edit: 刚才在我的手机IPhone 6 Plus上测试也是一样,画布不起作用,不能签名。 这个应该跟设备没有太大关系,直接使用chrome的模拟器就可以测试。 真不太明白是哪里没有加载出现这个问题的?
刷新就可以在画布上面签名了。。。。

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai ,我看到你的这个项目已经有一年没有更新了,还能看到你的回复真的挺意外的。不晓得你对于这个问题还有印象没? 这个问题我感觉大部分人使用可能都会遇到这个问题,需要刷新下页面画布才能签名。否则画布不起作用。。。。

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

稍等,我刚空下来,在看

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai , 多谢!

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

首先,你的代码是完全能运行的,还没有发现无法签名的情况,从其他路由跳过来也能正常签名。
唯一的不同在于,以下代码我本地没有store,因此注释了
image
请确认下,是否还有其他问题?

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

另外,补充下,项目代码拉下来默认存在两个路由//sign,两个路由之前的跳转也不会导致出现无法签名的情况,猜你可能是别的原因。

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

刚又测试了,使用this.$router.push({name: 'page1'})跳转至你粘贴的代码生成的路由,可以直接签名。
我使用chrome直接测试。

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai 嗯? 这个奇了怪了,我重新检查下现在。。。。 谢谢

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

我在项目中也是使用的这段代码,实际上,项目中的场景要更加复杂。
当时项目中工作流如下:

  1. 上个页面按下按钮,唤出签名页面
  2. 签名页旋转90°加载(因为app强制不横屏),并正常签名
  3. 签名后,直接进入预览页,然后才是提交到服务器

跟你的操作基本一致,目前线上没有收到无法签名反馈。
应该是兼容到iphone低版本的,以及Android 4.0左右
希望能给你带来信心~

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai 有什么办法debug这个问题吗?
我现在实在没有发现什么特别的代码或者组件没有加载,但是看这个问题应该是哪里没有加载?所以刷新页面就可以签名成功了,代码都是一样的啊,移植到自己的项目跳转到页面就是不能签名,需要刷新才能签名。

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

方便将你的项目剥离出一个简单的无法签名版本吗?
我尝试运行看看

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

还有一个问题就是发现如果签名的页面存在顶部返回按钮,这个时候的签名的笔画下来的是有一点的偏移的,这个是在这段代码里面的,这个应该怎么计算位置让画笔画下去的时候不会偏移:

 getHorizontalStyle () {
      const d = document
      const w = window.innerWidth || d.documentElement.clientWidth || d.body.clientWidth
      const h = window.innerHeight || d.documentElement.clientHeight || d.body.clientHeight
      let length = (h - w) / 2
      let width = w
      let height = h

      switch (this.degree) {
        case -90:
          length = -length
        /* eslint no-fallthrough: "error" */
        case 90:
          width = h
          height = w
          break
        default:
          length = 0
      }
      if (this.canvasBox) {
        this.canvasBox.removeChild(document.querySelector('canvas'))
        this.canvasBox.appendChild(document.createElement('canvas'))
        /* eslint  vue/no-async-in-computed-properties: "off" */
        setTimeout(() => {
          console.log(`测试执行`)
          this.initCanvas()
        }, 200)
      }
      return {
        transform: `rotate(${this.degree}deg) translate(${length}px,${length}px)`,
        width: `${width}px`,
        height: `${height}px`,
        transformOrigin: 'center center'
      }
    }

我现在就建一个项目,你帮我看下。多谢!

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai ,我新建了一个项目: https://github.com/ForkProjects/error-signature , 操作步骤如下:

$ cnpm i 
$ npm run start
$ 打开页面:http://127.0.0.1:3000
$ 点击“进入签名页面”按钮
$ 在对应页面画布签名不起作用
$ 刷新当前页面重新签名成功

重现几率100%
耽误你时间了,非常感谢!

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

终于等到了...

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

你引用的组件内有包含canvas名称的组件,因此每次运行vue会抛出如下错误:
image
猜测可能是这个原因,导致原生canvas解析变慢了,延迟100毫秒左右去执行initCanvas,便能成功初始化画布。
另外,建议把无关组件全部注释掉,然后试着debug,祝顺利~

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

@Louiszhai , 谢谢你的回复,你晚上工作的很晚啊!

这个签名的页面的代码是跟你的基本一样的代码。。。我又尝试了你提到的方法:

  1. 将对应的签名页面的getHorizontalStyle属性中的 this.initCanvas()设置了时间我100毫秒,重新运行,还是一样直接进去画布不能签名
  2. 将所有的无关代码注释掉,尝试重新运行,还是不行。

麻烦你能再帮我看下这个问题吗?新的修改的仓库代码在这里: https://github.com/ForkProjects/error-signature , 已经是最新的重现这个问题的代码。

Edit: debug发现getHorizontalStyle属性中的下面代码实际是没有执行:

if (this.canvasBox) {
        this.canvasBox.removeChild(document.querySelector('canvas'))
        this.canvasBox.appendChild(document.createElement('canvas'))
        /* eslint  vue/no-async-in-computed-properties: "off" */
        setTimeout(() => {
          console.log(`测试执行`)
          this.initCanvas()
        }, 100)
      }

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

好的,非常感谢,我看看 , @Louiszhai

from canvas-draw.

alterhu2020 avatar alterhu2020 commented on July 16, 2024

还是不行! 找不到原因了!

from canvas-draw.

leehave avatar leehave commented on July 16, 2024

我也遇到了,暂时是在initCanvas时先将画布清空。执行一次this.draw.clear(); 就没有这种情况了

from canvas-draw.

zzlit avatar zzlit commented on July 16, 2024

同样遇到了,模拟出了一种复现的情况:

<canvas ref="signature" style="width: 100%; height: 100%;" v-show="show"></canvas>

mounted() {
  this.init()
  setTimeout(() => {
    this.show = true
  }, 50)
}

methods: {
  init() {
    const canvas = this.$refs.signature;
    this.draw = new Draw(canvas, 0);
  }
}

添加了一个50毫秒的延迟,然后初始化还是正常初始化就可以模拟出这种情况,然后解决结果如下:

  // 源码中这个width height 是 100% 而不是 px 单位的,所以我在原型上加了一个判断方法
  let { width, height } = window.getComputedStyle(canvas, null); 

  Draw.prototype = {
    isDraw() {
    // 然后在调用这个的地方判断如果是false则重新初始化一遍就可以了
      if (this.width === '100%' && this.height === '100%') return false
      ...
    }
  }

from canvas-draw.

zzlit avatar zzlit commented on July 16, 2024

同样遇到了,模拟出了一种复现的情况:

<canvas ref="signature" style="width: 100%; height: 100%;" v-show="show"></canvas>

mounted() {
  this.init()
  setTimeout(() => {
    this.show = true
  }, 50)
}

methods: {
  init() {
    const canvas = this.$refs.signature;
    this.draw = new Draw(canvas, 0);
  }
}

添加了一个50毫秒的延迟,然后初始化还是正常初始化就可以模拟出这种情况,然后解决结果如下:

  // 源码中这个width height 是 100% 而不是 px 单位的,所以我在原型上加了一个判断方法
  let { width, height } = window.getComputedStyle(canvas, null); 

  Draw.prototype = {
    isDraw() {
    // 然后在调用这个的地方判断如果是false则重新初始化一遍就可以了
      if (this.width === '100%' && this.height === '100%') return false
      ...
    }
  }

更新一下,H5上又发现了一种复现情况,当上一个页面有滚动条的时候,如果滚动条是滑在下面进入签名页面的时候,这时候就不能签名,猜测其实是签了,可能是因为滚动条的原因导致坐标异常了看不到签的位置,如果在进入签名页面的时候把scrollTop置为0就可以正常签名了。

from canvas-draw.

Louiszhai avatar Louiszhai commented on July 16, 2024

辛苦,签名时,一定要剔除滚动条的干扰

from canvas-draw.

Related Issues (14)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.