- 审核失败:你的小程序涉及提供视频播放、观看等服务,请补充选择:文娱-其他视频类目?
小程序APPID:wx2d0f76331f4845d9 麻烦尽快查询我们该如何解决这个问题,着急上线。
2021-09-08 - 如何使用第三方 npm 扩展包
上一篇传参的示例中,不知道你是否注意到,像 [代码]isPast[代码](判断日期是否为过去) 或者 [代码]number[代码](距离当前的天数) 参数,根本就不需要传输嘛,这些应该是计算出来的。 然而,如果让你来写日期计算的 API,我相信绝大多数开发者,都不一定能够写出来,好在,这块有成熟的库可以使用,例如:[代码]moment.js[代码],还有 [代码]dayjs[代码],这篇文章就来介绍一下,如何在小程序中使用第三方 npm 扩展? 这块内容,官方文档已经写得比较清楚了,在工具 — 开发辅助 — npm 支持下,目录层次比较深,所以,在最开始的时候,建议通读一遍文档,还是非常有必要的。 npm 支持的要求 小程序并非从一开始就支持 npm,而是在基础库版本 2.2.1 或以上、及开发者工具 1.02.1808300 或以上的时候,才开始支持。 所以,这两个条件一定要注意,当然,如果才开始学习小程序开发,用最新版的就可以了。 安装 dayjs 用命令行进入项目目录,使用下面命令安装 dayjs [代码]npm init npm i dayjs --production [代码] 第一条命令是初始化 npm,第二条命令是安装 dayjs 包。 然后打开开发者工具,在本地设置中,勾选使用 npm 模块,如下图: [图片] 最后,打开工具菜单,点击构建 npm 即可。 [图片] 目录变化 构建完成后,项目目录中会多出几个文件: package.json package-lock.json node_modules miniprogram_npm 前面三个是 npm 初始化时,默认生成的,最后一个目录是开发者工具构建 npm 时生成的,可以理解为小程序版的 npm 包。 [图片] 使用方法与 node.js 开发一致,下面就来完善上一篇中的例子。 页面改写 打开 days 的 index.wxml 文件,将页面静态内容替换成变量,代码如下: [代码]<view class="list" bindtap='goto' data-title="{{ day.title }}" data-date="{{ day.date }}" data-isPast="{{ day.isPast }}" data-number="{{ day.number }}"> <view class="title">{{ day.title }}</view> <view class="date-number {{ day.isPast?'primary':'danger' }}">{{ day.number }}</view> <view class="date-desc">{{ day.isPast?"已过天数":"还剩天数" }}</view> <view class="desc">{{ day.date }}</view> </view> [代码] 可以看出,从逻辑层传过来一个 day 的对象。 逻辑层添加 day 对象数据,打开 days 目录的 index.js 文件,编写代码如下: [代码]data: { day: { date: '2019-10-01', title: '新中国成立 70 周年' } }, [代码] 这里只有 date 和 title 属性值,并没有 isPast 和 number,因为 isPast 和 number 值应该是实时计算出来的,而无需储存。 当前的页面是不完整的,接下来就是 [代码]dayjs[代码] 上场的时候了。 引入 dayjs 初始化数据 接着编辑 js 代码,引入 dayjs 模块,编写初始化数据函数,如下代码所示: [代码]// 引入 dayjs const dayjs = require('dayjs'); // 当天日期 const today = dayjs().format('YYYY-MM-DD'); // 初始化数据 const init_data = function(day){ let date = day.date; day.isPast = today > date; day.number = dayjs(date).diff(dayjs(today), 'day'); return day; } [代码] dayjs 的用法不多介绍了,可自行查看文档,[代码]init_data[代码] 函数在初始数据上,添加了两个参数 isPast 和 number,使用 dayjs 模块提供的 API 可以快速计算它们的值。 最后,在页面事件函数 [代码]onShow[代码] 中,调用初始化函数,代码如下: [代码]onShow: function () { this.setData({ day: init_data(this.data.day) }); }, [代码] 这样,整体代码就写完了,只需要一个 [代码]title[代码] 和 [代码]date[代码],就可以实时计算出相隔的天数,引入 [代码]dayjs[代码] 模块,让这个计算的过程,简单了许多。 总结 这篇文章介绍了 npm 包的引入,安装以及使用。并实际演示了 dayjs 日期库的使用,进而完善了上一篇中的例子。 这里留一个作业题,看能不能结合前面的内容,完成一个完整的「计算日子」功能。 PS. 目前 demo 中只缺少添加日期,显示日期列表,以及存储日期的功能了,正好这些知识点,前面已经都介绍过了。 写完这篇文章,距离国庆节还剩下 22 天。 [图片] 更多文章:https://github.com/pengloo53/miniprogram-articles
2019-09-09 - 初试小程序接入three.js
看着小程序下的canvas日渐完善,特别是2.7.0库下新增了WebGL,终于可以摆脱原来用wx.createCanvasContext创建的2d上下文(不知为何在使用魔改后three.js中的CanvasRenderer渲染画面就是很慢,捕获JavaScript Profiler看着就是慢在draw方法上)。 不过理想很丰满,现实很骨感,想要在小程序上用three.js依然要来个大改造。让我们开始吧 官方文档里提供了一段如何获取WebGL Context的代码: [代码]Page({[代码][代码] [代码][代码]onReady() {[代码][代码] [代码][代码]const query = wx.createSelectorQuery()[代码][代码] [代码][代码]query.select([代码][代码]'#myCanvas'[代码][代码]).node().exec((res) => {[代码][代码] [代码][代码]const canvas = res[0].node[代码][代码] [代码][代码]const gl = canvas.getContext([代码][代码]'webgl'[代码][代码])[代码][代码] [代码][代码]console.log(gl)[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码]})[代码]我们就从这里入手 首先先写个wxml: [代码]<[代码][代码]canvas[代码] [代码]type[代码][代码]=[代码][代码]"webgl"[代码] [代码]id[代码][代码]=[代码][代码]"webgl"[代码] [代码]width[代码][代码]=[代码][代码]"{{canvasWidth||(320*2)}}"[代码] [代码]height[代码][代码]=[代码][代码]"{{canvasHeight||(504*2)}}"[代码] [代码]style[代码][代码]=[代码][代码]'width:{{canvasStyleWidth||"320px"}};height:{{canvasStyleHeight||"504px"}};'[代码] [代码]bindtouchstart[代码][代码]=[代码][代码]'onTouchStart'[代码] [代码]bindtouchmove[代码][代码]=[代码][代码]'onTouchMove'[代码] [代码]bindtouchend[代码][代码]=[代码][代码]'onTouchEnd'[代码][代码]></[代码][代码]canvas[代码][代码]>[代码]其中width和height是设置画布大小的,style中的width和height是设置画布的实际渲染大小的 然后js: [代码]onReady:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]query = wx.createSelectorQuery().select([代码][代码]'#webgl'[代码][代码]).node().exec((res) => {[代码][代码] [代码][代码]var[代码] [代码]canvas = res[0].node;[代码][代码] [代码][代码]requestAnimationFrame = canvas.requestAnimationFrame;[代码][代码] [代码][代码]canvas.width = canvas._width;[代码][代码] [代码][代码]canvas.height = canvas._height;[代码][代码] [代码][代码]canvas.style = {};[代码][代码] [代码][代码]canvas.style.width = canvas.width;[代码][代码] [代码][代码]canvas.style.height = canvas.height;[代码][代码] [代码][代码]self.init(canvas);[代码][代码] [代码][代码]self.animate();[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码]先模拟dom构造一个canvas对象,然后传入init方法中,我们在这里创建场景、相机、渲染器等 [代码]init: [代码][代码]function[代码] [代码](canvas) {[代码][代码]...[代码][代码] [代码][代码]camera = [代码][代码]new[代码] [代码]THREE.PerspectiveCamera(20, canvas.width / canvas.height, 1, 10000);[代码][代码] [代码][代码]scene = [代码][代码]new[代码] [代码]THREE.Scene();[代码][代码]...[代码][代码] [代码][代码]renderer = [代码][代码]new[代码] [代码]THREE.WebGLRenderer({ canvas: canvas, antialias: [代码][代码]true[代码] [代码]});[代码][代码] [代码][代码]}[代码]这样一个最基础的三维场景就搭好了,然后继续执行animate方法,开始渲染场景 [代码]animate:[代码][代码]function[代码][代码]() {[代码][代码] [代码][代码]requestAnimationFrame([代码][代码]this[代码][代码].animate);[代码][代码] [代码][代码]this[代码][代码].render();[代码][代码] [代码][代码]}[代码]接下来尝试跑一下three.js提供的例子 webgl_geometry_colors : [图片] 锯齿问题比较严重,暂时没找到解决办法,但总体来说还是可以的,至少场景渲染出来了 由于暂时没想到如何改造CanvasTexture,我把例子中的 [代码]var[代码] [代码]canvas = document.createElement( [代码][代码]'canvas'[代码] [代码]);[代码][代码]canvas.width = 128;[代码][代码]canvas.height = 128;[代码][代码]var[代码] [代码]context = canvas.getContext( [代码][代码]'2d'[代码] [代码]);[代码][代码]var[代码] [代码]gradient = context.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2 );[代码][代码]gradient.addColorStop( 0.1, [代码][代码]'rgba(210,210,210,1)'[代码] [代码]);[代码][代码]gradient.addColorStop( 1, [代码][代码]'rgba(255,255,255,1)'[代码] [代码]);[代码][代码]context.fillStyle = gradient;[代码][代码]context.fillRect( 0, 0, canvas.width, canvas.height );[代码][代码]var[代码] [代码]shadowTexture = [代码][代码]new[代码] [代码]THREE.CanvasTexture( canvas );[代码]替换成 webgl_geometries 例子中的TextureLoader [代码]var[代码] [代码]shadowTexture = [代码][代码]new[代码] [代码]THREE.TextureLoader().load(canvas,[代码][代码]'../../textures/UV_Grid_Sm.jpg'[代码][代码]);[代码]可能有人会发现load方法中传入的参数多了一个canvas,因为小程序提供的api没法直接创建Image对象,仅有一个Canvas.createImage()方法可以创建Image对象。因此我们还需要改造一下TextureLoader中的load方法,先看一下原版中的load方法: [代码]Object.assign( TextureLoader.prototype, {[代码] [代码] [代码][代码]crossOrigin: [代码][代码]'anonymous'[代码][代码],[代码] [代码] [代码][代码]load: [代码][代码]function[代码] [代码]( url, onLoad, onProgress, onError ) {[代码] [代码] [代码][代码]var[代码] [代码]texture = [代码][代码]new[代码] [代码]Texture();[代码] [代码] [代码][代码]var[代码] [代码]loader = [代码][代码]new[代码] [代码]ImageLoader( [代码][代码]this[代码][代码].manager );[代码][代码] [代码][代码]loader.setCrossOrigin( [代码][代码]this[代码][代码].crossOrigin );[代码][代码] [代码][代码]loader.setPath( [代码][代码]this[代码][代码].path );[代码] [代码] [代码][代码]loader.load( url, [代码][代码]function[代码] [代码]( image ) {[代码]其中实际调用了ImageLoader来加载图片,在看看ImageLoader: [代码]Object.assign( ImageLoader.prototype, {[代码] [代码] [代码][代码]crossOrigin: [代码][代码]'anonymous'[代码][代码],[代码] [代码] [代码][代码]load: [代码][代码]function[代码] [代码]( url, onLoad, onProgress, onError ) {[代码] [代码] [代码][代码]if[代码] [代码]( url === undefined ) url = [代码][代码]''[代码][代码];[代码] [代码] [代码][代码]if[代码] [代码]( [代码][代码]this[代码][代码].path !== undefined ) url = [代码][代码]this[代码][代码].path + url;[代码] [代码] [代码][代码]url = [代码][代码]this[代码][代码].manager.resolveURL( url );[代码] [代码] [代码][代码]var[代码] [代码]scope = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]cached = Cache.get( url );[代码] [代码] [代码][代码]if[代码] [代码]( cached !== undefined ) {[代码] [代码] [代码][代码]scope.manager.itemStart( url );[代码] [代码] [代码][代码]setTimeout( [代码][代码]function[代码] [代码]() {[代码] [代码] [代码][代码]if[代码] [代码]( onLoad ) onLoad( cached );[代码] [代码] [代码][代码]scope.manager.itemEnd( url );[代码] [代码] [代码][代码]}, 0 );[代码] [代码] [代码][代码]return[代码] [代码]cached;[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]var[代码] [代码]image = document.createElementNS( [代码][代码]'http://www.w3.org/1999/xhtml'[代码][代码], [代码][代码]'img'[代码] [代码]);[代码] [代码] [代码][代码]function[代码] [代码]onImageLoad() {[代码] [代码] [代码][代码]image.removeEventListener( [代码][代码]'load'[代码][代码], onImageLoad, [代码][代码]false[代码] [代码]);[代码][代码] [代码][代码]image.removeEventListener( [代码][代码]'error'[代码][代码], onImageError, [代码][代码]false[代码] [代码]);[代码] [代码] [代码][代码]Cache.add( url, [代码][代码]this[代码] [代码]);[代码] [代码] [代码][代码]if[代码] [代码]( onLoad ) onLoad( [代码][代码]this[代码] [代码]);[代码] [代码] [代码][代码]scope.manager.itemEnd( url );[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]function[代码] [代码]onImageError( event ) {[代码] [代码] [代码][代码]image.removeEventListener( [代码][代码]'load'[代码][代码], onImageLoad, [代码][代码]false[代码] [代码]);[代码][代码] [代码][代码]image.removeEventListener( [代码][代码]'error'[代码][代码], onImageError, [代码][代码]false[代码] [代码]);[代码] [代码] [代码][代码]if[代码] [代码]( onError ) onError( event );[代码] [代码] [代码][代码]scope.manager.itemError( url );[代码][代码] [代码][代码]scope.manager.itemEnd( url );[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]image.addEventListener( [代码][代码]'load'[代码][代码], onImageLoad, [代码][代码]false[代码] [代码]);[代码][代码] [代码][代码]image.addEventListener( [代码][代码]'error'[代码][代码], onImageError, [代码][代码]false[代码] [代码]);[代码] [代码] [代码][代码]if[代码] [代码]( url.substr( 0, 5 ) !== [代码][代码]'data:'[代码] [代码]) {[代码] [代码] [代码][代码]if[代码] [代码]( [代码][代码]this[代码][代码].crossOrigin !== undefined ) image.crossOrigin = [代码][代码]this[代码][代码].crossOrigin;[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]scope.manager.itemStart( url );[代码] [代码] [代码][代码]image.src = url;[代码] [代码] [代码][代码]return[代码] [代码]image;[代码] [代码] [代码][代码]},[代码]document.createElementNS这种东西肯定是没法存在的,没办法,把canvas传进来用createImage方法创建Image对象,改造后: [代码]Object.assign( ImageLoader.prototype, {[代码] [代码] [代码][代码]crossOrigin: [代码][代码]'anonymous'[代码][代码],[代码] [代码] [代码][代码]load: [代码][代码]function[代码] [代码]( canvas,url, onLoad, onProgress, onError ) {[代码] [代码] [代码][代码]if[代码] [代码]( url === undefined ) url = [代码][代码]''[代码][代码];[代码] [代码] [代码][代码]if[代码] [代码]( [代码][代码]this[代码][代码].path !== undefined ) url = [代码][代码]this[代码][代码].path + url;[代码] [代码] [代码][代码]url = [代码][代码]this[代码][代码].manager.resolveURL( url );[代码] [代码] [代码][代码]var[代码] [代码]scope = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]cached = Cache.get( url );[代码] [代码] [代码][代码]if[代码] [代码]( cached !== undefined ) {[代码] [代码] [代码][代码]scope.manager.itemStart( url );[代码] [代码] [代码][代码]setTimeout( [代码][代码]function[代码] [代码]() {[代码] [代码] [代码][代码]if[代码] [代码]( onLoad ) onLoad( cached );[代码] [代码] [代码][代码]scope.manager.itemEnd( url );[代码] [代码] [代码][代码]}, 0 );[代码] [代码] [代码][代码]return[代码] [代码]cached;[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]//var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );[代码][代码] [代码][代码]console.log([代码][代码]this[代码][代码], canvas);[代码][代码] [代码][代码]var[代码] [代码]image = canvas.createImage();[代码] [代码] [代码][代码]function[代码] [代码]onImageLoad() {[代码] [代码] [代码][代码]//image.removeEventListener( 'load', onImageLoad, false );[代码][代码] [代码][代码]//image.removeEventListener( 'error', onImageError, false );[代码][代码] [代码][代码]image.onload = [代码][代码]function[代码] [代码]() { };[代码][代码] [代码][代码]image.onerror = [代码][代码]function[代码] [代码]() { };[代码] [代码] [代码][代码]Cache.add( url, [代码][代码]this[代码] [代码]);[代码] [代码] [代码][代码]if[代码] [代码]( onLoad ) onLoad( [代码][代码]this[代码] [代码]);[代码] [代码] [代码][代码]scope.manager.itemEnd( url );[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]function[代码] [代码]onImageError( event ) {[代码] [代码] [代码][代码]//image.removeEventListener( 'load', onImageLoad, false );[代码][代码] [代码][代码]//image.removeEventListener( 'error', onImageError, false );[代码][代码] [代码][代码]image.onload = [代码][代码]function[代码] [代码]() { };[代码][代码] [代码][代码]image.onerror = [代码][代码]function[代码] [代码]() { };[代码] [代码] [代码][代码]if[代码] [代码]( onError ) onError( event );[代码] [代码] [代码][代码]scope.manager.itemEnd( url );[代码][代码] [代码][代码]scope.manager.itemError( url );[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]//image.addEventListener( 'load', onImageLoad, false );[代码][代码] [代码][代码]//image.addEventListener( 'error', onImageError, false );[代码][代码] [代码][代码]image.onload = onImageLoad;[代码][代码] [代码][代码]image.onerror = onImageError;[代码] [代码] [代码][代码]if[代码] [代码]( url.substr( 0, 5 ) !== [代码][代码]'data:'[代码] [代码]) {[代码] [代码] [代码][代码]if[代码] [代码]( [代码][代码]this[代码][代码].crossOrigin !== undefined ) image.crossOrigin = [代码][代码]this[代码][代码].crossOrigin;[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]scope.manager.itemStart( url );[代码] [代码] [代码][代码]image.src = url;[代码] [代码] [代码][代码]return[代码] [代码]image;[代码] [代码] [代码][代码]},[代码]然后TextureLoader的load方法也改一下传参顺序: [代码]Object.assign( TextureLoader.prototype, {[代码] [代码] [代码][代码]crossOrigin: [代码][代码]'anonymous'[代码][代码],[代码] [代码] [代码][代码]load: [代码][代码]function[代码] [代码]( canvas,url, onLoad, onProgress, onError ) {[代码] [代码] [代码][代码]var[代码] [代码]texture = [代码][代码]new[代码] [代码]Texture();[代码] [代码] [代码][代码]var[代码] [代码]loader = [代码][代码]new[代码] [代码]ImageLoader( [代码][代码]this[代码][代码].manager );[代码][代码] [代码][代码]loader.setCrossOrigin( [代码][代码]this[代码][代码].crossOrigin );[代码][代码] [代码][代码]loader.setPath( [代码][代码]this[代码][代码].path );[代码] [代码] [代码][代码]loader.load( canvas,url, [代码][代码]function[代码] [代码]( image ) {[代码]OK! 这个例子代码我放在https://github.com/leo9960/xcx_threejs,大家可以接着研究一下。潜力还是比较大的,比如我拿它搞了个全景展示 [图片] ---------------------------------------------------------------------- 2019.5.26 新上传了全景展示的范例,基于panolens.js,欢迎围观
2019-05-26 - 生成分享朋友圈微海报
[图片] 描码体验 文未有小程序源码地址 (另有偿提供后台数据接口服务) [图片] 模板代码参考 [代码] <view class='padding text-center'> <image src="{{shareImage}}" mode="widthFix"></image> <canvasdrawer :painting.sync="painting" @getImage.user="eventGetImage"></canvasdrawer> </view> <view class="padding"> <button wx:if="{{shareImage}}" class='cu-btn block bg-red margin-tb-sm lg' @tap="eventSave">保存分享图</button> </view> [代码] 部分js代码 [代码]event = { getImage (e) { wepy.showToast({ title: e, icon: "success", duration: 2000 }); } } async buildPoster(){ if(this.shareImage == ''){ wepy.showLoading({ title: "生成中", mask: true }); let poster = await commApi.GetArticlePoster(this.id) this.painting = poster } this.showposter = true; this.$apply(); } methods = { async eventSave() { // 保存图片至本地 const res = await wepy.saveImageToPhotosAlbum({ filePath: this.shareImage }); if (res.errMsg === "saveImageToPhotosAlbum:ok") { wepy.showToast({ title: "分享图已保存到相册", icon: "success", duration: 2000 }); } }, eventGetImage(event) { wepy.hideLoading(); const { tempFilePath, errMsg } = event; if (errMsg === "canvasdrawer:ok"){ this.shareImage = tempFilePath; }else{ wepy.showToast({ title: errMsg, icon: "success", duration: 2000 }); } }, } [代码] 后端php代码 [代码]public function defalutArticlePoster($app, $user, $article){ // $title_length = Str::length($article->title,'UTF-8'); // dd($title_length); $poster = [ 'width'=>460, 'height'=> 500, 'clear'=> true, 'views'=>[ [ 'type'=> 'rect', 'background'=> '#666', 'top'=> 0, 'width'=> 460, 'height'=> 500, 'left'=> 0 ], [ 'type'=> 'rect', 'background'=> '#ffffff', 'top'=> 2, 'width'=> 456, 'height'=> 496, 'left'=> 2 ], [ 'type'=> 'image', 'url'=> $article->cover? $article->cover:'https://wx1.wechatrank.com/base64img/20190402233111.jpeg', 'top'=> 70, 'left'=> 28, 'width'=> 400, 'height'=> 320 ], [ 'type'=> 'text', 'content'=> $article->title, 'fontSize'=> 18, 'lineHeight'=> 24, 'color'=> '#333', 'textAlign'=> 'left', 'top'=> $title_length>22?16:26, 'left'=> 28, 'width'=> 387, 'MaxLineNumber'=> 2, 'breakWord'=> true, 'bolder'=> true ], [ 'type'=> 'text', 'content'=> str_replace(array(" ", " ", "\t", "\n", "\r", "\r\n", PHP_EOL), '', $article->intro), 'fontSize'=> 18, 'lineHeight'=> 24, 'color'=> '#666', 'textAlign'=> 'left', 'top'=> 406, 'left'=> 28, 'width'=> 310, 'MaxLineNumber'=> 3, 'breakWord'=> true, 'bolder'=> true ], [ 'type'=> 'image', 'url'=> url('/qrcode/article/'.$article->qrcode), 'top'=> 406, 'left'=> 360, 'width'=> 68, 'height'=> 68 ] ], ]; return $poster; } [代码] 示例代码来源: https://github.com/yizenghui/wxcms/blob/master/src/pages/poster/index.wpy 项目地址: https://github.com/yizenghui/wxcms https://github.com/simmzl/wepy_canvas_drawer 觉得对您有帮助请点个赞,谢谢
2019-05-27 - HQChart教程 - 如何快速的创建一个K线图
HQChart介绍 HQChart(https://github.com/jones2000/HQChart)是根据c++行情客户端软件移植到js平台上的一个项目。由金融图形库和麦语法(分析加语法)脚本执行器组成,所有指标计算都在前端完成,后台api只需要提供基础的行情数据就可以。 支持小程序,h5, 分2套代码完成,因为个个平台对canvas的支持和性能是不一样的,所有独立2套代码,最大的发挥个个平台canvas的性能。 支持手势操作(放大缩小,左右移动,十字光标) 。。。。。其他功能我就不介绍了,有兴趣的可以上github上看,上面有详细的使用教程和设计方案 创建K线图步骤 在WXML里面增加一个’canvas’元素, 并设置canvas-id, 定义好手势事件,bindtouchstart,bindtouchmove,bindtouchend [代码]<view class="container"> <canvas class="historychart" canvas-id="historychart" style="height:{{Height}}px; width:{{Width}}px;" bindtouchstart='historytouchstart' bindtouchmove='historytouchmove' bindtouchend='historytouchend'/> </view> [代码] 在对应的页面js文件里面 import HQChart组件 (wechathqchart/umychart.wechat.3.0.js) [代码]import { JSCommon } from "../wechathqchart/umychart.wechat.3.0.js"; [代码] 在page类里面创建HQChart组件,设置参数并绑定到’canvas’元素上 [代码]Page( { data: { Height: 0, Width: 0, }, HistoryChart: null, HistoryOption: { Type: '历史K线图', //如果要横屏类型使用:历史K线图横屏 Windows: //窗口指标 [ { Index: 'MA' }, { Index: 'VOL'}, { Index: 'RSI' } ], //ColorIndex: { Index: '五彩K线-双飞乌鸦' }, //五彩K线 //TradeIndex: { Index: '交易系统-BIAS' }, //交易系统 Symbol: "000001.sz", IsAutoUpate: true, //是自动更新数据 IsShowCorssCursorInfo: true, //是否显示十字光标的刻度信息 CorssCursorTouchEnd:true, //手离开屏幕 隐藏十字光标 KLine: { DragMode: 1, //拖拽模式 0 禁止拖拽 1 数据拖拽 2 区间选择 Right: 1, //复权 0 不复权 1 前复权 2 后复权 Period: 0, //周期 0 日线 1 周线 2 月线 3 年线 MaxReqeustDataCount: 1000, //数据个数 PageSize: 30, //一屏显示多少数据 Info: ["业绩预告", "公告", "互动易", "调研", "大宗交易", "龙虎榜"], //信息地雷 //Policy: ["30天地量", "20日均线,上穿62日均线"], //策略信息 InfoDrawType:0, DrawType: 0, }, //叠加股票 Overlay: [ //{Symbol:'000001.sz'} ], KLineTitle: //标题设置 { IsShowName: true, //显示股票名称 IsShowSettingInfo: true, //显示周期/复权 }, Border: //边框 { Left: 1, //左边间距 Right: 1, //右边间距 Top:30, Bottom:25, }, Frame: //子框架设置 [ { SplitCount: 3 }, { SplitCount: 2 }, { SplitCount: 2 }, ], ExtendChart: //扩展图形 [ { Name: 'KLineTooltip' } ], }, onLoad: function () { var self = this // 获取系统信息 wx.getSystemInfo({ success: function (res) { console.log(res); // 可使用窗口宽度、高度 //console.log('height=' + res.windowHeight); //console.log('width=' + res.windowWidth); self.setData({ Height: res.windowHeight, Width: res.windowWidth, Title: { Display:'block'}}); } }); }, onReady: function () { //创建历史K线类 var element = new JSCommon.JSCanvasElement(); element.ID = 'historychart'; element.Height = this.data.Height; //高度宽度需要手动绑定!! 微信没有元素类 element.Width = this.data.Width; this.HistoryChart = JSCommon.JSChart.Init(element); //把画布绑定到行情模块中 this.HistoryChart.SetOption(this.HistoryOption); }, //把画图事件绑定到hqchart控件上 historytouchstart: function (event) { if (this.HistoryChart) this.HistoryChart.OnTouchStart(event); }, historytouchmove: function (event) { if (this.HistoryChart) this.HistoryChart.OnTouchMove(event); }, historytouchend: function (event) { if (this.HistoryChart) this.HistoryChart.OnTouchEnd(event); }, }) [代码] 这样1个K线图+指标图就完成了 效果图 [图片] [图片] HQChart代码地址 地址: https://github.com/jones2000/HQChart
2019-06-15 - 随手写的一个css头像轮播,代码较菜,不喜勿喷.哈哈哈
[图片] 大概就长这样样子,可以向左自动轮播的那种,主要就是用到了css的延迟transition-delay这个属性,然后配合定时器做的一个css的轮播.哈哈哈,然后没了,欢迎各位改造,指出里面的错误.觉得想用的朋友就拿去用吧 代码链接:https://developers.weixin.qq.com/s/ox93u6m17W9X
2019-06-13 - 小程序如何生成海报分享朋友圈
摘要: 小程序开发必备技能啊… 原文:小程序如何生成海报分享朋友圈 作者:小白 Fundebug经授权转载,版权归原作者所有。 项目需求写完有一段时间了,但是还是想回过来总结一下,一是对项目的回顾优化等,二是对坑的地方做个记录,避免以后遇到类似的问题。 需求 利用微信强大的社交能力通过小程序达到裂变的目的,拉取新用户。 生成的海报如下: [图片] 需求分析 1、利用小程序官方提供的api可以直接分享转发到微信群打开小程序 2、利用小程序生成海报保存图片到相册分享到朋友圈,用户长按识别二维码关注公众号或者打开小程序来达到裂变的目的 实现方案 一、分析如何实现 相信大家应该都会有类似的迷惑,就是如何按照产品设计的那样绘制成海报,其实当时我也是不知道如何下手,认真想了下得通过canvas绘制成图片,这样用户保存这个图片到相册,就可以分享到朋友圈了。但是要绘制的图片上面不仅有文字还有数字、图片、二维码等且都是活的,这个要怎么动态生成呢。认真想了下,需要一点一点的将文字和数字,背景图绘制到画布上去,这样通过api最终合成一个图片导出到手机相册中。 二、需要解决的问题 二维码的动态获取和绘制(包括如何生成小程序二维码、公众号二维码、打开网页二维码) 背景图如何绘制,获取图片信息 将绘制完成的图片保存到本地相册 处理用户是否取消授权保存到相册 三、实现步骤 这里我具体写下围绕上面所提出的问题,描述大概实现的过程 ①首先创建canvas画布,我把画布定位设成负的,是为了不让它显示在页面上,是因为我尝试把canvas通过判断条件动态的显示和隐藏,在绘制的时候会出现问题,所以采用了这种方法,这里还有一定要设置画布的大小。 [代码]<canvas canvas-id="myCanvas" style="width: 690px;height:1085px;position: fixed;top: -10000px;"></canvas> [代码] ②创建好画布之后,先绘制背景图,因为背景图我是放在本地,所以获取 <canvas> 组件 canvas-id 属性,通过createCanvasContext创建canvas的绘图上下文 CanvasContext 对象。使用drawImage绘制图像到画布,第一个参数是图片的本地地址,后面两个参数是图像相对画布左上角位置的x轴和y轴,最后两个参数是设置图像的宽高。 [代码]const ctx = wx.createCanvasContext('myCanvas') ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085) [代码] ③创建好背景图后,在背景图上绘制头像,文字和数字。通过getImageInfo获取头像的信息,这里需要注意下在获取的网络图片要先配置download域名才能生效,具体在小程序后台设置里配置。 获取头像地址,首先量取头像在画布中的大小,和x轴Y轴的坐标,这里的result[0]是我用promise封装返回的一个图片地址 [代码]let headImg = new Promise(function (resolve) { wx.getImageInfo({ src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`, success: function (res) { resolve(res.path) }, fail: function (err) { console.log(err) wx.showToast({ title: '网络错误请重试', icon: 'loading' }) } }) }) let avatarurl_width = 60, //绘制的头像宽度 avatarurl_heigth = 60, //绘制的头像高度 avatarurl_x = 28, //绘制的头像在画布上的位置 avatarurl_y = 36; //绘制的头像在画布上的位置 ctx.save(); // 先保存状态 已便于画完圆再用 ctx.beginPath(); //开始绘制 //先画个圆 前两个参数确定了圆心 (x,y) 坐标 第三个参数是圆的半径 四参数是绘图方向 默认是false,即顺时针 ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false); ctx.clip(); //画了圆 再剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片 [代码] 这里举个例子说下如何绘制文字,比如我要绘制如下这个“字”,需要动态获取前面字数的总宽度,这样才能设置“字”的x轴坐标,这里我本来是想通过measureText来测量字体的宽度,但是在iOS端第一次获取的宽度值不对,关于这个问题,我还在微信开发者社区提了bug,所以我想用另一个方法来实现,就是先获取正常情况下一个字的宽度值,然后乘以总字数就获得了总宽度,亲试是可以的。 [图片] [代码]let allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325; ctx.font = 'normal normal 30px sans-serif'; ctx.setFillStyle('#ffffff') ctx.fillText('字', allReading, 150); [代码] ④绘制公众号二维码,和获取头像是一样的,也是先通过接口返回图片网络地址,然后再通过getImageInfo获取公众号二维码图片信息 ⑤如何绘制小程序码,具体官网文档也给出生成无限小程序码接口,通过生成的小程序可以打开任意一个小程序页面,并且二维码永久有效,具体调用哪个小程序二维码接口有不同的应用场景,具体可以看下官方文档怎么说的,也就是说前端通过传递参数调取后端接口返回的小程序码,然后绘制在画布上(和上面写的绘制头像和公众号二维码一样的) [代码]ctx.drawImage('小程序码的本地地址', x轴, Y轴, 宽, 高) [代码] ⑥最终绘制完把canvas画布转成图片并返回图片地址 [代码] wx.canvasToTempFilePath({ canvasId: 'myCanvas', success: function (res) { canvasToTempFilePath = res.tempFilePath // 返回的图片地址保存到一个全局变量里 that.setData({ showShareImg: true }) wx.showToast({ title: '绘制成功', }) }, fail: function () { wx.showToast({ title: '绘制失败', }) }, complete: function () { wx.hideLoading() wx.hideToast() } }) [代码] ⑦保存到系统相册;先判断用户是否开启用户授权相册,处理不同情况下的结果。比如用户如果按照正常逻辑授权是没问题的,但是有的用户如果点击了取消授权该如何处理,如果不处理会出现一定的问题。所以当用户点击取消授权之后,来个弹框提示,当它再次点击的时候,主动跳到设置引导用户去开启授权,从而达到保存到相册分享朋友圈的目的。 [代码]// 获取用户是否开启用户授权相册 if (!openStatus) { wx.openSetting({ success: (result) => { if (result) { if (result.authSetting["scope.writePhotosAlbum"] === true) { openStatus = true; wx.saveImageToPhotosAlbum({ filePath: canvasToTempFilePath, success() { that.setData({ showShareImg: false }) wx.showToast({ title: '图片保存成功,快去分享到朋友圈吧~', icon: 'none', duration: 2000 }) }, fail() { wx.showToast({ title: '保存失败', icon: 'none' }) } }) } } }, fail: () => { }, complete: () => { } }); } else { wx.getSetting({ success(res) { // 如果没有则获取授权 if (!res.authSetting['scope.writePhotosAlbum']) { wx.authorize({ scope: 'scope.writePhotosAlbum', success() { openStatus = true wx.saveImageToPhotosAlbum({ filePath: canvasToTempFilePath, success() { that.setData({ showShareImg: false }) wx.showToast({ title: '图片保存成功,快去分享到朋友圈吧~', icon: 'none', duration: 2000 }) }, fail() { wx.showToast({ title: '保存失败', icon: 'none' }) } }) }, fail() { // 如果用户拒绝过或没有授权,则再次打开授权窗口 openStatus = false console.log('请设置允许访问相册') wx.showToast({ title: '请设置允许访问相册', icon: 'none' }) } }) } else { // 有则直接保存 openStatus = true wx.saveImageToPhotosAlbum({ filePath: canvasToTempFilePath, success() { that.setData({ showShareImg: false }) wx.showToast({ title: '图片保存成功,快去分享到朋友圈吧~', icon: 'none', duration: 2000 }) }, fail() { wx.showToast({ title: '保存失败', icon: 'none' }) } }) } }, fail(err) { console.log(err) } }) } [代码] 总结 至此所有的步骤都已实现,在绘制的时候会遇到一些异步请求后台返回的数据,所以我用promise和async和await进行了封装,确保导出的图片信息是完整的。在绘制的过程确实遇到一些坑的地方。比如初开始导出的图片比例大小不对,还有用measureText测量文字宽度不对,多次绘制(可能受网络原因)有时导出的图片上的文字颜色会有误差等。如果你也遇到一些比较坑的地方可以一起探讨下做个记录,下面附下完整的代码 [代码]import regeneratorRuntime from '../../utils/runtime.js' // 引入模块 const app = getApp(), api = require('../../service/http.js'); var ctx = null, // 创建canvas对象 canvasToTempFilePath = null, // 保存最终生成的导出的图片地址 openStatus = true; // 声明一个全局变量判断是否授权保存到相册 // 获取微信公众号二维码 getCode: function () { return new Promise(function (resolve, reject) { api.fetch('/wechat/open/getQRCodeNormal', 'GET').then(res => { console.log(res, '获取微信公众号二维码') if (res.code == 200) { console.log(res.content, 'codeUrl') resolve(res.content) } }).catch(err => { console.log(err) }) }) }, // 生成海报 async createCanvasImage() { let that = this; // 点击生成海报数据埋点 that.setData({ generateId: '点击生成海报' }) if (!ctx) { let codeUrl = await that.getCode() wx.showLoading({ title: '绘制中...' }) let code = new Promise(function (resolve) { wx.getImageInfo({ src: codeUrl, success: function (res) { resolve(res.path) }, fail: function (err) { console.log(err) wx.showToast({ title: '网络错误请重试', icon: 'loading' }) } }) }) let headImg = new Promise(function (resolve) { wx.getImageInfo({ src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`, success: function (res) { resolve(res.path) }, fail: function (err) { console.log(err) wx.showToast({ title: '网络错误请重试', icon: 'loading' }) } }) }) Promise.all([headImg, code]).then(function (result) { const ctx = wx.createCanvasContext('myCanvas') console.log(ctx, app.globalData.ratio, 'ctx') let canvasWidthPx = 690 * app.globalData.ratio, canvasHeightPx = 1085 * app.globalData.ratio, avatarurl_width = 60, //绘制的头像宽度 avatarurl_heigth = 60, //绘制的头像高度 avatarurl_x = 28, //绘制的头像在画布上的位置 avatarurl_y = 36, //绘制的头像在画布上的位置 codeurl_width = 80, //绘制的二维码宽度 codeurl_heigth = 80, //绘制的二维码高度 codeurl_x = 588, //绘制的二维码在画布上的位置 codeurl_y = 984, //绘制的二维码在画布上的位置 wordNumber = that.data.wordNumber, // 获取总阅读字数 // nameWidth = ctx.measureText(that.data.wordNumber).width, // 获取总阅读字数的宽度 // allReading = ((nameWidth + 375) - 325) * 2 + 380; // allReading = nameWidth / app.globalData.ratio + 325; allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325; console.log(wordNumber, wordNumber.toString().length, allReading, '获取总阅读字数的宽度') ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085) ctx.save(); // 先保存状态 已便于画完圆再用 ctx.beginPath(); //开始绘制 //先画个圆 前两个参数确定了圆心 (x,y) 坐标 第三个参数是圆的半径 四参数是绘图方向 默认是false,即顺时针 ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false); ctx.clip(); //画了圆 再剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片 ctx.restore(); //恢复之前保存的绘图上下文状态 可以继续绘制 ctx.setFillStyle('#ffffff'); // 文字颜色 ctx.setFontSize(28); // 文字字号 ctx.fillText(that.data.currentChildren.name, 103, 78); // 绘制文字 ctx.font = 'normal bold 44px sans-serif'; ctx.setFillStyle('#ffffff'); // 文字颜色 ctx.fillText(wordNumber, 325, 153); // 绘制文字 ctx.font = 'normal normal 30px sans-serif'; ctx.setFillStyle('#ffffff') ctx.fillText('字', allReading, 150); ctx.font = 'normal normal 24px sans-serif'; ctx.setFillStyle('#ffffff'); // 文字颜色 ctx.fillText('打败了全国', 26, 190); // 绘制文字 ctx.font = 'normal normal 24px sans-serif'; ctx.setFillStyle('#faed15'); // 文字颜色 ctx.fillText(that.data.percent, 154, 190); // 绘制孩子百分比 ctx.font = 'normal normal 24px sans-serif'; ctx.setFillStyle('#ffffff'); // 文字颜色 ctx.fillText('的小朋友', 205, 190); // 绘制孩子百分比 ctx.font = 'normal bold 32px sans-serif'; ctx.setFillStyle('#333333'); // 文字颜色 ctx.fillText(that.data.singIn, 50, 290); // 签到天数 ctx.fillText(that.data.reading, 280, 290); // 阅读时长 ctx.fillText(that.data.reading, 508, 290); // 听书时长 // 书籍阅读结构 ctx.font = 'normal normal 28px sans-serif'; ctx.setFillStyle('#ffffff'); // 文字颜色 ctx.fillText(that.data.bookInfo[0].count, 260, 510); ctx.fillText(that.data.bookInfo[1].count, 420, 532); ctx.fillText(that.data.bookInfo[2].count, 520, 594); ctx.fillText(that.data.bookInfo[3].count, 515, 710); ctx.fillText(that.data.bookInfo[4].count, 492, 828); ctx.fillText(that.data.bookInfo[5].count, 348, 858); ctx.fillText(that.data.bookInfo[6].count, 212, 828); ctx.fillText(that.data.bookInfo[7].count, 148, 726); ctx.fillText(that.data.bookInfo[8].count, 158, 600); ctx.font = 'normal normal 18px sans-serif'; ctx.setFillStyle('#ffffff'); // 文字颜色 ctx.fillText(that.data.bookInfo[0].name, 232, 530); ctx.fillText(that.data.bookInfo[1].name, 394, 552); ctx.fillText(that.data.bookInfo[2].name, 496, 614); ctx.fillText(that.data.bookInfo[3].name, 490, 730); ctx.fillText(that.data.bookInfo[4].name, 466, 850); ctx.fillText(that.data.bookInfo[5].name, 323, 878); ctx.fillText(that.data.bookInfo[6].name, 184, 850); ctx.fillText(that.data.bookInfo[7].name, 117, 746); ctx.fillText(that.data.bookInfo[8].name, 130, 621); ctx.drawImage(result[1], codeurl_x, codeurl_y, codeurl_width, codeurl_heigth); // 绘制头像 ctx.draw(false, function () { // canvas画布转成图片并返回图片地址 wx.canvasToTempFilePath({ canvasId: 'myCanvas', success: function (res) { canvasToTempFilePath = res.tempFilePath that.setData({ showShareImg: true }) console.log(res.tempFilePath, 'canvasToTempFilePath') wx.showToast({ title: '绘制成功', }) }, fail: function () { wx.showToast({ title: '绘制失败', }) }, complete: function () { wx.hideLoading() wx.hideToast() } }) }) }) } }, // 保存到系统相册 saveShareImg: function () { let that = this; // 数据埋点点击保存学情海报 that.setData({ saveId: '保存学情海报' }) // 获取用户是否开启用户授权相册 if (!openStatus) { wx.openSetting({ success: (result) => { if (result) { if (result.authSetting["scope.writePhotosAlbum"] === true) { openStatus = true; wx.saveImageToPhotosAlbum({ filePath: canvasToTempFilePath, success() { that.setData({ showShareImg: false }) wx.showToast({ title: '图片保存成功,快去分享到朋友圈吧~', icon: 'none', duration: 2000 }) }, fail() { wx.showToast({ title: '保存失败', icon: 'none' }) } }) } } }, fail: () => { }, complete: () => { } }); } else { wx.getSetting({ success(res) { // 如果没有则获取授权 if (!res.authSetting['scope.writePhotosAlbum']) { wx.authorize({ scope: 'scope.writePhotosAlbum', success() { openStatus = true wx.saveImageToPhotosAlbum({ filePath: canvasToTempFilePath, success() { that.setData({ showShareImg: false }) wx.showToast({ title: '图片保存成功,快去分享到朋友圈吧~', icon: 'none', duration: 2000 }) }, fail() { wx.showToast({ title: '保存失败', icon: 'none' }) } }) }, fail() { // 如果用户拒绝过或没有授权,则再次打开授权窗口 openStatus = false console.log('请设置允许访问相册') wx.showToast({ title: '请设置允许访问相册', icon: 'none' }) } }) } else { // 有则直接保存 openStatus = true wx.saveImageToPhotosAlbum({ filePath: canvasToTempFilePath, success() { that.setData({ showShareImg: false }) wx.showToast({ title: '图片保存成功,快去分享到朋友圈吧~', icon: 'none', duration: 2000 }) }, fail() { wx.showToast({ title: '保存失败', icon: 'none' }) } }) } }, fail(err) { console.log(err) } }) } }, [代码]
2019-06-15 - 前端跨域问题及解决方案
1、同源策略 同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。一个源的定义:如果协议,端口(如果指定了一个)和主机对于两个页面是相同的,则两个页面具有相同的源。 下表给出了相对 http://store.company.com/dir/page.html 同源检测的示例: URL 结果 原因 http://store.company.com/dir/inner/another.html 成功 同一域名 http://store.company.com/dir2/other.html 成功 同一域名下不同文件夹 https://store.company.com/secure.html 失败 不同的协议 ( https ) http://store.company.com:81/dir/etc.html 失败 不同的端口 ( 81 ) http://news.company.com/dir/other.html 失败 不同的主机 ( news ) 2、主域相同的跨域 document.domain 的场景只适用于不同子域的框架间的交互,及主域必须相同的不同源。 页面可能会更改其自己的来源,但有一些限制。脚本可以将 document.domain 的值设置为其当前域或其当前域的超级域。如果将其设置为其当前域的超级域,则较短的域将用于后续原始检查。例如,假设文档中的一个脚本在 http://store.company.com/dir/other.html 执行以下语句: [代码] document.domain = "company.com"; [代码] 这条语句执行之后,页面将会成功地通过对 http://company.com/dir/page.html 的同源检测。 注:浏览器单独保存端口号。任何的赋值操作,包括 document.domain = document.domain 都会以 null 值覆盖掉原来的端口号。因此,company.com:8080 页面的脚本不能仅通过设置 document.domain = “company.com” 就能与 company.com 通信。赋值时必须带上端口号,以确保端口号不会为 null 。 [代码] (1) 在www.a.com/a.html中: document.domain = 'a.com'; var ifr = document.createElement('iframe'); ifr.src = 'http://www.script.a.com/b.html'; ifr.display = none; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; ifr.onload = null; }; (2) 在www.script.a.com/b.html中: document.domain = 'a.com'; // 注意:使用 document.domain 允许子域安全访问其父域时,您需要设置 document 域在父域和子域中具有相同的值。这是必要的,即使这样做只是将父域设置回其原始值。否则可能会导致权限错误。这里都是 a.com 。 [代码] 3、完全不同源的跨域(两个页面之间的通信) 3.1、通过 location.hash 跨域 假设域名 a.com 下的文件 cs1.html 要和 jianshu.com 域名下的 cs2.html 传递信息。 1、cs1.html 首先创建自动创建一个隐藏的 iframe,iframe 的 src 指向 jianshu.com 域名下的 cs2.html 页面。 2、cs2.html 响应请求后再将通过修改 cs1.html 的 hash 值来传递数据。 3、同时在 cs1.html 上加一个定时器,隔一段时间来判断 location.hash 的值有没有变化,一旦有变化则获取获取 hash 值。 注:由于两个页面不在同一个域下 IE、Chrome 不允许修改 parent.location.hash 的值,所以要借助于 a.com 域名下的一个代理 iframe 。 优点:1.可以解决域名完全不同的跨域。2.可以实现双向通讯。 缺点:location.hash 会直接暴露在 URL 里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。另外由于 URL 大小的限制,支持传递的数据量也不大。有些浏览器不支持 onhashchange 事件,需要轮询来获知 URL 的变化。 3.2、通过 window.name 跨域 window 对象有个 name 属性,该属性有个特征:即在一个窗口 window 的生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的。window.name 属性的神奇之处在于 name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。 [代码] window.name = data; // 父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入。 location = 'http://parent.url.com/xxx.html'; // 接着,子窗口跳回一个与主窗口同域的网址。 var data = document.getElementById('myFrame').contentWindow.name。// 然后,主窗口就可以读取子窗口的 window.name 了。 [代码] 如果是与 iframe 通信的场景就需要把 iframe 的 src 设置成当前域的一个页面地址。 3.3、通过 window.postMessage 跨域 HTML5 为了解决这个问题,引入了一个全新的 API:跨文档通信 API(Cross-document messaging)。这个 API 为 window 对象新增了一个 window.postMessage 方法,允许跨窗口通信,不论这两个窗口是否同源。postMessag e的兼容性如下: [图片] 可以看到 Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都将支持这个功能。但是 Internet Explorer 8 和 9 以及 Firefox 6.0和更低版本仅支持字符串作为 postMessage 的消息。 [代码] var popup = window.open('http://bbb.com', 'title'); // 父窗口 http://aaa.com 向子窗口 http://bbb.com 发消息,调用 postMessage 方法。 popup.postMessage('Hello World!', 'http://bbb.com'); [代码] postMessage 方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为 *,表示不限制域名,向所有窗口发送。 父窗口和子窗口都可以通过 message 事件,监听对方的消息。message 事件的事件对象 event ,提供以下三个属性: 1、event.source:发送消息的窗口。 2、event.origin: 消息发向的网址。 3、event.data:消息内容。 一个例子: [代码] var onmessage = function (event) { var data = event.data; // 消息 var origin = event.origin; // 消息来源地址 var source = event.source; // 源Window对象 if(origin == "http://www.aaa.com"){ console.log(data);// hello world! } source.postMessage('Nice to see you!', '*'); }; if (typeof window.addEventListener != 'undefined') { window.addEventListener('message', onmessage, false); } else if (typeof window.attachEvent != 'undefined') { // ie window.attachEvent('onmessage', onmessage); } [代码] 4、请求不同源的跨域 4.1、通过 JSONP 跨域 基本原理:网页通过添加一个 <script> 元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。例子如下: [代码] function todo(data){ console.log('The author is: '+ data.name); } var script = document.createElement('script'); script.src = 'http://www.jianshu.com/author?callback=todo'; // 向服务器 www.jianshu.com 发出请求。注意,该请求的查询字符串有一个 callback 参数,用来指定回调函数的名字。 document.body.appendChild(script); // 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。 todo({"name": "fewjq"}); // 由于 <script> 元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 todo 函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象。 [代码] 优点:简单适用,老式浏览器全部支持,服务器改造小。不需要 XMLHttpRequest 或 ActiveX 的支持。 缺点:只支持 GET 请求。 4.2、通过 WebSocket 跨域 WebSocket 是一种通信协议,使用 ws://(非加密)和 wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。 4.3、通过 CORS 跨域 当一个资源请求一个其它域名的资源时会发起一个跨域 HTTP 请求(cross-origin HTTP request)。比如说,域名 A 的某 Web 应用通过 <img> 标签引入了域名 B 的某图片资源,域名 A 的 Web 应用就会导致浏览器发起一个跨域 HTTP 请求。在当今的 Web 开发中,使用跨域 HTTP 请求加载各类资源(包括 CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。 正如大家所知,出于安全考虑,浏览器会拦截跨域请求的返回结果。比如,使用 XMLHttpRequest 对象和 Fetch 发起 HTTP 请求就必须遵守“同源策略”。 具体而言,Web 应用程序通过 XMLHttpRequest 对象或 Fetch 能且只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。为了能开发出更强大、更丰富、更安全的 Web 应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。比如,可以使用 XMLHttpRequest 发起跨站 HTTP 请求。 [图片] 隶属于 W3C 的 Web 应用工作组推荐了一种新的机制,即跨源资源共享(Cross-Origin Resource Sharing ) CORS。这种机制让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。需要特别注意的是,这个规范是针对API容器的(比如说XMLHttpReques 或者 Fetch),以减轻跨域HTTP请求的风险。 跨源资源共享标准( cross-origin sharing standard) 使得以下场景可以使用跨站 HTTP 请求: 1、使用 XMLHttpRequest 或 Fetch发起跨站 HTTP 请求。 2、Web 字体 (CSS 中通过 @font-face 使用跨站字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。 3、WebGL 贴图 4、使用drawImage绘制 5、Images/video 画面到canvas. 6、样式表(使用 CSSOM) 7、Scripts (for unmuted exceptions) 所有浏览器都支持该功能,IE浏览器不能低于IE10。通过 XMLHttpRequest 对象发起。但Internet Explorer 8 和 9 可以通过 XDomainRequest 对象来实现 CORS 。可以把 CORS 分为:简单请求、预请求和附带凭证信息的请求。 4.3.1、简单请求 所谓的简单,是指: (1)只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型 (Content-Type) 只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain 中的一种。 (2)不会使用自定义请求头(类似于 X-Modified 这种)。 一个例子: [代码] // 比如说,假如站点 http://foo.example 的网页应用想要访问 http://bar.other 的资源。以下的 JavaScript 代码应该会在 foo.example 上执行: var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/public-data/'; function callOtherDomain() { if(invocation) { invocation.open('GET', url, true); invocation.onreadystatechange = handler; invocation.send(); } } [代码] [图片] [代码] // 让我们看看,在这个场景中,浏览器会发送什么的请求到服务器,而服务器又会返回什么给浏览器: GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example // 该请求来自于 http://foo.exmaple。 / /以上是浏览器发送请求 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * // 这表明服务器接受来自任何站点的跨站请求。如果设置为 http://foo.example 。其它站点就不能跨站访问 http://bar.other 的资源了。 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml // 以上是服务器返回信息给浏览器 [代码] 如上,通过使用 Origin 和 Access-Control-Allow-Origin 就可以完成最简单的跨站请求。不过服务器需要把 Access-Control-Allow-Origin 设置为 * 或者包含由 Origin 指明的站点。 4.3.2、预请求 不同于上面讨论的简单请求,“预请求”要求必须先发送一个 OPTIONS 请求给目的站点,来查明这个跨站请求对于目的站点是不是安全可接受的。这样做,是因为跨站请求可能会对目的站点的数据造成破坏。 当请求具备以下条件,就会被当成预请求处理: (1)请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。 (2)使用自定义请求头(比如添加诸如 X-PINGOTHER) 一个例子: [代码] var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/post-here/'; var body = '{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}Arun'; function callOtherDomain(){ if(invocation){ invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/xml'); invocation.onreadystatechange = handler; invocation.send(body); } } [代码] 如上,以 XMLHttpRequest 创建了一个 POST 请求,为该请求添加了一个自定义请求头(X-PINGOTHER: pingpong),并指定数据类型为 application/xml。所以,该请求是一个“预请求”形式的跨站请求。浏览器使用一个 OPTIONS 发送了一个“预请求”。Firefox 3.1 根据请求参数,决定需要发送一个“预请求”,来探明服务器端是否接受后续真正的请求。 OPTIONS 是 HTTP/1.1 里的方法,用来获取更多服务器端的信息,是一个不应该对服务器数据造成影响的方法。 随同 OPTIONS 请求,以下两个请求头一起被发送: [代码] Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER [代码] 假设服务器成功响应返回部分信息如下: [代码] Access-Control-Allow-Origin: http://foo.example // 表明服务器允许 http://foo.example 的请求 Access-Control-Allow-Methods: POST, GET, OPTIONS // 表明服务器可以接受 POST, GET 和 OPTIONS 的请求方法 Access-Control-Allow-Headers: X-PINGOTHER // 传递一个可接受的自定义请求头列表。服务器也需要设置一个与浏览器对应。否则会报 Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers in preflight response 的错误 Access-Control-Max-Age: 1728000 // 告诉浏览器,本次“预请求”的响应结果有效时间是多久。在上面的例子里,1728000 秒代表着 20 天内,浏览器在处理针对该服务器的跨站请求,都可以无需再发送“预请求”,只需根据本次结果进行判断处理。 [代码] 4.3.3、附带凭证信息的请求 XMLHttpRequest 和访问控制功能,最有趣的特性就是,发送凭证请求(HTTP Cookies和验证信息)的功能。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将 XMLHttpRequest 的一个特殊标志位设置为true,浏览器就将允许该请求的发送。 一个例子: [代码] // http://foo.example 站点的脚本向 http://bar.other 站点发送一个 GET 请求,并设置了一个 Cookies 值。脚本代码如下: var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/credentialed-content/'; function callOtherDomain(){ if(invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); } } [代码] 如上,第七行代码将 XMLHttpRequest 的withCredentials 标志设置为 true ,从而使得 Cookies 可以随着请求发送。因为这是一个简单的 GET 请求,所以浏览器不会发送一个“预请求”。但是,如果服务器端的响应中,如果没有返回 Access-Control-Allow-Credentials: true 的响应头,那么浏览器将不会把响应结果传递给发出请求的脚本程序,以保证信息的安全。 假设服务器成功响应返回部分信息如下: [代码] Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Credentials: true Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT [代码] 如果 bar.other 的响应头里没有 Access-Control-Allow-Credentials:true ,则响应会被忽略.。特别注意: 给一个带有 withCredentials 的请求发送响应的时候,服务器端必须指定允许请求的域名,不能使用“*”。上面这个例子中,如果响应头是这样的 Access-Control-Allow-Origin:* ,则响应会失败。在这个例子里,因为 Access-Control-Allow-Origin 的值是 http://foo.example 这个指定的请求域名,所以客户端把带有凭证信息的内容被返回给了客户端。另外注意,更多的 cookie 信息也被创建了。 CORS 的优点:CORS 支持所有类型的 HTTP 请求,是跨域 HTTP 请求的根本解决方案。 5、参考文献 HTTP访问控制(CORS)
2019-06-21 - json2canvas:使用JSON生成小程序海报
作者:诗人的咸鱼 原文:小程序生成分享海报,一个json就够了。同时支持web Fundebug经授权转载,版权归原作者所有。 需求 在项目里写过几个canvas生成分享海报页面后,觉得这是个重复且冗余的工作.于是就想有没有能通过类似json直接生成海报的库. 然后就在github找到到两个项目: wxa-plugin-canvas,不太喜欢配置文件的写法.就没多去了解 mp_canvas_drawer,使用方式就比较符合直觉,不过可惜功能有点少. 然后就想着能不能自己再造个轮子.于是就有了这个项目 json2canvas,你可以简单的理解为是mp_canvas_drawer的增强版吧. json2canvas canvas绘制海报,写个json就够了. 项目的canvas绘制是基于cax实现的.所以天然的带来一个好处,json2canvas同时支持小程序和web 功能 支持缩放. 如果设计稿是750,而画布只有375时.你不需要任何换算,只需要将scale设置为0.5即可. 支持文本(长文本自动换行,感谢 coolzjy@v2ex 提供的正则 https://regexr.com/4f12l ,优化了换行的计算方式(不会粗暴的折断单词)) 支持图片(圆角) 支持圆型,矩形,矩形圆角 支持分组(cax里很好用的一个功能) 同时支持小程序和web 示例 web-demo 界面左边的json,可以进行编辑,直接看效果哟~ 小程序-demo [代码]git clone https://github.com/willnewii/json2canvas.git 微信开发者工具导入项目 example/weapp/ [代码] 小程序安装 [代码]npm i json2canvas 微信开发者工具->工具->构建npm [代码] 在需要使用的界面引入Component [代码]{ "usingComponents": { "json2canvas":"/miniprogram_npm/json2canvas/index" } } [代码] 效果图 想要生成一个这样的海报,需要怎么做?(红框是图片元素,蓝框是文字元素,其余的是一张背景图。) [图片] 一个json就搞定.具体支持的元素和参数,请查看项目readme [代码]{ "width": 750, "height": 1334, "scale": 0.5, "children": [ { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/bg_concerts_1.jpg", "width": 750, "height": 1334 }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/wxapp_code.jpg", "width": 100, "x": 48, "y": 44, "isCircular": true, }, { "type": "circle", "r": 50, "lineWidth": 5, "strokeStyle": "#CCCCCC", "x": 48, "y": 44, }, { "type": "text", "text": "歌词本", "font": "30px Arial", "color": "#FFFFFF", "x": 168, "y": 75, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/medal_concerts_1.png", "width": 300, "x": "center", "y": 361 }, { "type": "text", "text": "一生活一场 五月天", "font": "38px Arial", "color": "#FFFFFF", "x": "center", "y": 838, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "text", "text": "北京6场,郑州2场,登船,上班,听到你想听的歌了吗?", "font": "24px Arial", "color": "#FFFFFF", "x": "center", "y": 888, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "rect", "width": 750, "height": 193, "fillStyle": "#FFFFFF", "x": 0, "y": "bottom" }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/wxapp_code.jpg", "width": 117, "height": 117, "x": 47, "y": 1180 }, { "type": "text", "text": "长按识别小程序二维码", "font": "26px Arial", "color": "#858687", "x": 192, "y": 1202 }, { "type": "text", "text": "加入五月天 永远不会太迟", "font": "18px Arial", "color": "#A4A5A6", "x": 192, "y": 1249 }] } [代码] 问题反馈 有什么问题可以直接提issue
2019-06-29