yeungx

yeungx 查看完整档案

广州编辑广州华立科技职业技术学院  |  计算机网络 编辑万兔斯瑞科技  |  前端 编辑 www.yeungx.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

yeungx 赞了回答 · 2月19日

解决一个div进行旋转或缩放后,怎么获取它变形后的宽高?比如:

//获得视觉上的宽, 高... 原答案直接用了长宽一样的模型, 导致结果出错了

//1.获得原始宽, 高
var el = document.getElementById('box'),
    h = el.offsetHeight,
    w = el.offsetWidth;
    
//2.获得旋转角度
//matrix(cosθ, sinθ, -sinθ, cosθ, 0, 0)
var transform = window.getComputedStyle(el)['transform'],
    rotate = transform.slice(7, -1).split(',');

//3.计算宽度, 高度
var lastH = h * rotate[0] + w * rotate[1],   //h * sinθ + w * cosθ
    lastW = h * rotate[1] + w * rotate[0];   //h * cosθ + w * sinθ

图片描述

关注 6 回答 4

yeungx 赞了文章 · 2月19日

从Vue到Electron

前言

本文介绍一种使用vue-cli-plugin-electron-builder(名字太长,以下简称builder),将现有的Vue单页项目迁移到Electron的做法。

下面将会以著名的Vue Element Admin为例,详细讲述如何把它变成一个双端项目

选型

说到Vue迁移到Electron,网上许多资料都是介绍如何使用electron-vue,但经过对比与实践,我选择使用builder,原因如下:

  1. builder的更新维护更及时,而且issues更少,相比之下,electron-vue的更新基本停滞了,问题也更多

  1. electron-vue使用的electron版本很旧,而builder使用的electron跟得上官方的步伐
  • electron-vue用的是2.0.4的electron:

  • builder的electron版本现在是11.2.1:

  1. electron-vue是一套基于vue-cli2的项目模版,而builder是基于vue-cli3的脚手架插件

项目实践也证明这个选择比较明智^_^

迁移步骤

进入正题

首先当然是找到你的Vue项目,本文以Vue Element Admin为例,OK,让我们先下载这个项目:

git clone https://github.com/PanJiaChen/vue-element-admin.git

第二步,安装依赖:

cd vue-element-admin
npm i

跑起来看看:

npm run dev

前两步走完,我们就有了个棒棒的Vue项目:

第三步,安装electron项目所需的依赖:

不多,基本的就4个:

npm i -D electron@^9.0.0
npm i -D electron-devtools-installer@^3.1.0
npm i -D electron-icon-builder@^2.0.1
npm i -D vue-cli-plugin-electron-builder@~2.0.0-rc.5

安装依赖的时候可能会遇到两类问题:

  1. electron安装十分缓慢,甚至装不上
  2. 证书错误

第一类问题可以通过设置镜像来解决:

npm config set registry https://registry.npm.taobao.org/
npm set electron_mirror https://npm.taobao.org/mirrors/electron/
npm set ELECTRON_MIRROR https://cdn.npm.taobao.org/dist/electron/

证书错误多见于在公司安装时发生,与公司使用的代理有关,可以通过如下方法解决:

npm config set strict-ssl false
// 注意,把https都变成http:
npm config set registry http://registry.npm.taobao.org/
npm set electron_mirror http://npm.taobao.org/mirrors/electron/
npm set ELECTRON_MIRROR http://cdn.npm.taobao.org/dist/electron/

第四步,划分主进程与渲染进程

如果你使用vue-cli-plugin-electron-builder来创建项目,它默认是将主进程的源文件设置为background.js,与渲染进程的文件同放在src目录下。

我还是更喜欢electron-webpack的做法:在src下分出两个目录:main和renderer。main放置主进程相关的代码,而renderer放置渲染进程相关的代码。electron-vue也是这么做的。

OK,我们也在src下分出两个目录:mainrenderer,然后将原来src下的模块都放进renderer中

在main中创建index.js(主进程代码),填入如下代码:

import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])

async function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      <% if (spectronSupport) { %>
      // Required for Spectron testing
      enableRemoteModule: true,
      <% } %>
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
    }
  })

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      await installExtension(VUEJS_DEVTOOLS)
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  }
  createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

第五步,配置vue.config.js:

注意pluginOptions.electronBuilder.mainProcessFile和configureWebpack中的config.entry.app,配置的详情可见:buildercli,还要特别注意对svg的处理。

module.exports = {
  pluginOptions: {
    electronBuilder: {
      mainProcessFile: 'src/main/index.js',
      builderOptions: {
        appId: "catpoint.com",
        productName: "Catpoint",
        icon: "./public/icons/icon.ico",
        files: ["**/*", "static/*"],
        asar: true,
        mac: {
          icon: "./public/icon.png",
          target: ["zip", "dmg"],
          category: "com.catpoint-category.utilities"
        },
        win: {
          icon: "./public/icons/icon.ico",
          target: ["zip", "nsis"]
        },
        nsis: {
          oneClick: false,
          allowElevation: true,
          allowToChangeInstallationDirectory: true,
          installerIcon: "./public/icons/icon.ico",
          uninstallerIcon: "./public/icons/icon.ico",
          installerHeaderIcon: "./public/icons/icon.ico",
          createDesktopShortcut: true,
          createStartMenuShortcut: true,
          license: "./LICENSE.txt"
        }
      }
    }
  },
  ...
  configureWebpack: config => {
    config.entry.app = './src/renderer/main.js'
    return {
      name: name,
      resolve: {
        alias: {
          '@': resolve('src/renderer')
        }
      }
    }
  },
  ...
  chainWebpack(config) {
    config.module
      .rule('svg')
      .exclude.add(resolve('src/renderer/icons'))
      .end()
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/renderer/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
     .end()
  }
}

第六步,配置脚本命令:

"scripts": {
    "dev:desktop": "npm run copy && vue-cli-service electron:serve --mode dev",
    "dev:web": "vue-cli-service serve",
    "build:desktop": "npm run build:icon && npm run build:mac && npm run build:win",
    "build:win": "vue-cli-service electron:build --legacy --windows --x64",
    "build:mac": "vue-cli-service electron:build --legacy --macos",
    "build:icon": "electron-icon-builder --input=./public/icon.png --output=public --flatten",
    "build:web": "vue-cli-service build",
    ...
},

好了,到这里就完成了迁移,并且支持双端的调试和打包,顺利的话,半个小时就好了。运行npm run dev:desktop和npm run dev:web看看效果吧。

您也可以看看这个:Proton Admin,一个在Vue Element Admin基础上改建而来的Electron项目,在持续改进和增强中。

查看原文

赞 1 收藏 0 评论 0

yeungx 关注了标签 · 2月19日

electron

Electron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。
Electron 通过将 Chromium 和 Node.js 合并到同一个运行时环境中,并将其打包为 Mac,Windows 和 Linux 系统下的应用来实现这一目的。

关注 467

yeungx 赞了问题 · 1月19日

解决正则匹配问题

我想从 {t('word')} {t("word1")} {t(`word2`)} {t(word3)} 捕获出这个数组 ['word', 'word1', 'word2', 'word3']

我的正则表达式是这个 /(?<=t\(([`|'|"]?))(.*?)(?=\1\))/g.

但是,我捕获的数组里都像这样"'word'"带有引号。

所以我应该怎么改进能达到对应的效果呢?

关注 4 回答 4

yeungx 赞了回答 · 1月19日

解决正则匹配问题

const string = "{t('word')} {t(\"word1\")} {t(`word2`)} {t(word3)}";

string.match(/(?<=(?:^|\s)\u007b\u0074\u0028([\u0022\u0027\u0060]?))[^\u0022\u0027\u0060]+(?=\1\u0029\u007d(?:\s|$))/g);

关注 4 回答 4

yeungx 回答了问题 · 1月19日

解决正则匹配问题

\{t\([\'\"]?(\w+)[\'\"]?\)\}

image.png

关注 4 回答 4

yeungx 收藏了文章 · 2020-12-30

《前端图形学从入门到放弃》002 教练我想学矩阵

本文大纲

  • 矩阵和线性变换是什么?
  • webgl如何实现缩放和旋转?
  • 平移不是线性变换,那该怎么办?
  • webgl如何实现平移?

今天的主菜是“矩阵”

在上一篇中我们已经实现了使用webgl绘制图形这个小目标《前端图形学从入门到放弃》001 画一个三角形
今天我们来探讨一个新的话题矩阵
我们都知道空间中的点我们可以用向量表示,例如二维平面中的点(1,1)就表示第一象限的点:
image
而多个点就能组成图形,这也是上一篇文章中我们说过的。
实际生产中这些图形往往并不会固定在画面中不懂,例如我们可以对图形进行旋转,缩放,移动。
实际上这个过程就是将图形的顶点组进行了旋转,缩放,移动,成为了新的顶点组,再由新的顶点组绘制成新的图形。

例如我们要将由点A(0,0),B(1,0),C(0,1)组成的三角形放大一倍,那么我们很容易知道放大后的点ÂḂĆ的坐标

Âx = Ax2 = 02 = 0
Ây = Ay2 = 02 = 0
Ḃx = Bx2 = 12 = 2
Ḃy = By2 = 02 = 0
Ćx = Cx2 = 02 = 0
Ćy = Cy2 = 12 = 2

数学家嫌这一番操作太过麻烦,而点又是可以写成向量形式的,要是能把操作简化成Â = M*A的形式就再好不过了,于是

    ⎡ 2  0 ⎤  
 = ⎪      ⎪ * A
    ⎣ 0  2 ⎦  

真是一顿操作猛如虎,一句不懂二百五

解剖矩阵

举证代表了一种计算,如上我们使用了一个二维矩阵

    ⎡ A  B ⎤  
    ⎣ C  D ⎦  

与一个二维向量相乘,会得到一个新的二维向量,计算公式如下

    ⎡ A  B ⎤  ⎡x⎤  =   ⎡ A*x + B*y ⎤
    ⎣ C  D ⎦  ⎣y⎦      ⎣ C*x + D*y ⎦

当然矩阵也不仅仅可以和向量相乘也可以和举证相乘,矩阵也不仅仅可以是22,也可以是33,更可以是n*m(n代表行数,m代表列数)。
两个矩阵可以相乘只需要,前一个矩阵的列数和后一个矩阵的函数相等即可。
例如nm的举证可以和ml的矩阵相乘,得到n*l的矩阵。
至于计算方法不是本文讨论的内容,推荐观看3blue1brown的视频。

缩放矩阵 与 旋转矩阵

而上文我们看到的矩阵

    ⎡ 2  0 ⎤  
    ⎣ 0  2 ⎦  

就是一个把任意点放大两倍的矩阵,更一般的,如果可以写出缩放矩阵(n≠0)

    ⎡ n  0 ⎤  ⎡x⎤  =   ⎡ n*x ⎤
    ⎣ 0  n ⎦  ⎣y⎦      ⎣ n*y ⎦

相比于缩放还有一种操作也很高频,那就是旋转。前面没有提到,矩阵的变换是线性的。什么叫做线性?也是是说同样的操作(放大2倍)对A点产生的效果,和对B点产生的效果(放大2倍)是一样的。
所以对于旋转矩阵我们也可以找到特殊的点进行求解,从而得到普遍适用的矩阵
对于x轴上的点a,旋转ø角后,可以用下图描述
image
我们就得到了二维平面上的旋转矩阵

    ⎡ cosø  -sinø ⎤  
    ⎣ sinø   cosø ⎦  

webgl和矩阵更配哟~

下面我们把矩阵和webgl结合起来,让《前端图形学从入门到放弃》001 画一个三角形中我们实现的三角形可以旋转与缩放
首先我们在页面上添加两个滑块分辨实现旋转与缩放

<canvas id="canvas" width="1000" height="1000"></canvas>
<div id="control">
    <input type="range" value=".75" min=".5" max="1" step="0.01" id="scale" value="0">
    <input type="range" value="0" min="0" max="360" step="0.01" id="rotate" value="0">
</div>

由于旋转和缩放操作仅仅影响顶点位置,下面我们之需要修改顶点着色器即可:

<script id="vertex-shader-2d" type="notjs">
    attribute vec2 vertPosition;
    attribute vec3 vertColor;
    varying vec3 fragColor;
    // 额外申明两个矩阵用于旋转和缩放
    uniform mat2 scaleMatrix;
    uniform mat2 rotateMatrix;
    void main() {
      fragColor = vertColor;
      // 把顶点坐标与矩阵相乘,得到旋转和缩放后的新顶点,传给gl
      gl_Position = vec4(scaleMatrix*rotateMatrix*vertPosition,0.0,1.0);
    }
   
  </script>

这两个申明的变量也要在js中取出

...
var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition');
var colorAttribLocation = gl.getAttribLocation(program, 'vertColor');
var scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
var rotateMatrix = gl.getUniformLocation(program, 'rotateMatrix');
...

由于我们期望在滑动滑块时,页面实时变化,因此需要一个loop函数来完成这一切:

....
 gl.useProgram(program);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
      loop(gl, rotateMatrix, scaleMatrix);
    }
...

loop函数:

var scaleNode = document.querySelector("#scale");
var rotateNode = document.querySelector("#rotate");
function loop(gl, rotateMatrix, scaleMatrix) {
      var angle = rotateNode.value/180*Math.PI;
      var scale = scaleNode.value;
      var sin = Math.sin(angle);//旋转角度正弦值
      var cos = Math.cos(angle);//旋转角度余弦值
      var myArr = new Float32Array([cos, -sin, sin, cos,]);
      var scaleArr = new Float32Array([scale, 0, 0, scale,]);
      gl.uniformMatrix2fv(rotateMatrix, false, myArr);
      gl.uniformMatrix2fv(scaleMatrix, false, scaleArr);
      gl.clearColor(0.75, 0.85, 0.8, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      requestAnimationFrame(function () {
        loop(gl, rotateMatrix, scaleMatrix);
      });
      gl.drawArrays(gl.TRIANGLES, 0, 3);
    }

大功告成:
image

教练我动不了了

不知道各位看官有没有发现,在矩阵这套线性变化下,我们没办法做平移操作。因为作为原点的o(0,0)不论乘以什么矩阵,结果都还是自己。但是平移操作是日常工作中极其常见的操作,不能平移甚至无法实现拖拽!
难道图形学之路就此gg?

但天无绝人之路,只要零点不是零点我就可以移动它,对于二维平面,我可以把它看作三维世界中一个不过原点的平面,原本的(x,y)变为(x,y,1)
此时就可以实现平移
image

根据上文,我们已经了解的矩阵知识,不难写出
image
而这种通过n+1维实现了n维线性变换外加移动操作的变换,就被称为齐次变换

webgl和齐次变换更配哟~

下面我们继续改造原有的webgl代码!
首先我们还需要加入两个滑块分别控制,图形上下和左右运动

<div id="control">
   ...
    <input type="range" value="0" min="-0.5" max="0.5" step="0.01" id="tranX">
    <input type="range" value="0" min="-.5" max=".5" step="0.01" id="tranY">
  </div>

由于齐次变换将所有的矩阵都升维了,我们需要改造定点着色器。

<script id="vertex-shader-2d" type="notjs">
    ...
    // 将原本二维矩阵定义为三维
    uniform mat3 scaleMatrix; 
    uniform mat3 rotateMatrix;
    uniform mat3 transformMatrix;
    void main() {
      fragColor = vertColor;
      vec3 v = rotateMatrix*scaleMatrix*transformMatrix*vec3(vertPosition,1.0);
      // 由于我们之需要x,y把他们取出即可
      gl_Position = vec4(v.xy,0.0,1.0);
    }
   
  </script>

由于矩阵从二维变为三维,取出的变量也需要重新定义为三维:

...
var trMatrix = gl.getUniformLocation(program,'transformMatrix');
      var scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
      var rotateMatrix = gl.getUniformLocation(program, 'rotateMatrix');
...
// 获取滑块
var tranXNode = document.querySelector("#tranX");
var tranYNode = document.querySelector("#tranY");
// 修改loop函数
...
    loop(gl, rotateMatrix, scaleMatrix,trMatrix);
}
function loop(gl, rotateMatrix, scaleMatrix,trMatrix) {
...
    var myArr = new Float32Array([cos, sin, 0 , -sin, cos, 0,0,0,1]);
    var scaleArr = new Float32Array([scale, 0, 0,0, scale,0,0,0,1]);
    var tranArr = new Float32Array([1,0,0,0,1,0,tranXNode.value,tranYNode.value,1]);
    // console.log(tranXNode.value);
    gl.uniformMatrix3fv(rotateMatrix, false, myArr);
    gl.uniformMatrix3fv(scaleMatrix, false, scaleArr);
    gl.uniformMatrix3fv(trMatrix, false, tranArr);
....

大功告成:
image

下期预告

我想二维的世界,大家也腻了,下篇我们将进入三维世界,并说说光线是如何影响物体的

查看原文

yeungx 赞了文章 · 2020-12-30

《前端图形学从入门到放弃》002 教练我想学矩阵

本文大纲

  • 矩阵和线性变换是什么?
  • webgl如何实现缩放和旋转?
  • 平移不是线性变换,那该怎么办?
  • webgl如何实现平移?

今天的主菜是“矩阵”

在上一篇中我们已经实现了使用webgl绘制图形这个小目标《前端图形学从入门到放弃》001 画一个三角形
今天我们来探讨一个新的话题矩阵
我们都知道空间中的点我们可以用向量表示,例如二维平面中的点(1,1)就表示第一象限的点:
image
而多个点就能组成图形,这也是上一篇文章中我们说过的。
实际生产中这些图形往往并不会固定在画面中不懂,例如我们可以对图形进行旋转,缩放,移动。
实际上这个过程就是将图形的顶点组进行了旋转,缩放,移动,成为了新的顶点组,再由新的顶点组绘制成新的图形。

例如我们要将由点A(0,0),B(1,0),C(0,1)组成的三角形放大一倍,那么我们很容易知道放大后的点ÂḂĆ的坐标

Âx = Ax2 = 02 = 0
Ây = Ay2 = 02 = 0
Ḃx = Bx2 = 12 = 2
Ḃy = By2 = 02 = 0
Ćx = Cx2 = 02 = 0
Ćy = Cy2 = 12 = 2

数学家嫌这一番操作太过麻烦,而点又是可以写成向量形式的,要是能把操作简化成Â = M*A的形式就再好不过了,于是

    ⎡ 2  0 ⎤  
 = ⎪      ⎪ * A
    ⎣ 0  2 ⎦  

真是一顿操作猛如虎,一句不懂二百五

解剖矩阵

举证代表了一种计算,如上我们使用了一个二维矩阵

    ⎡ A  B ⎤  
    ⎣ C  D ⎦  

与一个二维向量相乘,会得到一个新的二维向量,计算公式如下

    ⎡ A  B ⎤  ⎡x⎤  =   ⎡ A*x + B*y ⎤
    ⎣ C  D ⎦  ⎣y⎦      ⎣ C*x + D*y ⎦

当然矩阵也不仅仅可以和向量相乘也可以和举证相乘,矩阵也不仅仅可以是22,也可以是33,更可以是n*m(n代表行数,m代表列数)。
两个矩阵可以相乘只需要,前一个矩阵的列数和后一个矩阵的函数相等即可。
例如nm的举证可以和ml的矩阵相乘,得到n*l的矩阵。
至于计算方法不是本文讨论的内容,推荐观看3blue1brown的视频。

缩放矩阵 与 旋转矩阵

而上文我们看到的矩阵

    ⎡ 2  0 ⎤  
    ⎣ 0  2 ⎦  

就是一个把任意点放大两倍的矩阵,更一般的,如果可以写出缩放矩阵(n≠0)

    ⎡ n  0 ⎤  ⎡x⎤  =   ⎡ n*x ⎤
    ⎣ 0  n ⎦  ⎣y⎦      ⎣ n*y ⎦

相比于缩放还有一种操作也很高频,那就是旋转。前面没有提到,矩阵的变换是线性的。什么叫做线性?也是是说同样的操作(放大2倍)对A点产生的效果,和对B点产生的效果(放大2倍)是一样的。
所以对于旋转矩阵我们也可以找到特殊的点进行求解,从而得到普遍适用的矩阵
对于x轴上的点a,旋转ø角后,可以用下图描述
image
我们就得到了二维平面上的旋转矩阵

    ⎡ cosø  -sinø ⎤  
    ⎣ sinø   cosø ⎦  

webgl和矩阵更配哟~

下面我们把矩阵和webgl结合起来,让《前端图形学从入门到放弃》001 画一个三角形中我们实现的三角形可以旋转与缩放
首先我们在页面上添加两个滑块分辨实现旋转与缩放

<canvas id="canvas" width="1000" height="1000"></canvas>
<div id="control">
    <input type="range" value=".75" min=".5" max="1" step="0.01" id="scale" value="0">
    <input type="range" value="0" min="0" max="360" step="0.01" id="rotate" value="0">
</div>

由于旋转和缩放操作仅仅影响顶点位置,下面我们之需要修改顶点着色器即可:

<script id="vertex-shader-2d" type="notjs">
    attribute vec2 vertPosition;
    attribute vec3 vertColor;
    varying vec3 fragColor;
    // 额外申明两个矩阵用于旋转和缩放
    uniform mat2 scaleMatrix;
    uniform mat2 rotateMatrix;
    void main() {
      fragColor = vertColor;
      // 把顶点坐标与矩阵相乘,得到旋转和缩放后的新顶点,传给gl
      gl_Position = vec4(scaleMatrix*rotateMatrix*vertPosition,0.0,1.0);
    }
   
  </script>

这两个申明的变量也要在js中取出

...
var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition');
var colorAttribLocation = gl.getAttribLocation(program, 'vertColor');
var scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
var rotateMatrix = gl.getUniformLocation(program, 'rotateMatrix');
...

由于我们期望在滑动滑块时,页面实时变化,因此需要一个loop函数来完成这一切:

....
 gl.useProgram(program);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
      loop(gl, rotateMatrix, scaleMatrix);
    }
...

loop函数:

var scaleNode = document.querySelector("#scale");
var rotateNode = document.querySelector("#rotate");
function loop(gl, rotateMatrix, scaleMatrix) {
      var angle = rotateNode.value/180*Math.PI;
      var scale = scaleNode.value;
      var sin = Math.sin(angle);//旋转角度正弦值
      var cos = Math.cos(angle);//旋转角度余弦值
      var myArr = new Float32Array([cos, -sin, sin, cos,]);
      var scaleArr = new Float32Array([scale, 0, 0, scale,]);
      gl.uniformMatrix2fv(rotateMatrix, false, myArr);
      gl.uniformMatrix2fv(scaleMatrix, false, scaleArr);
      gl.clearColor(0.75, 0.85, 0.8, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      requestAnimationFrame(function () {
        loop(gl, rotateMatrix, scaleMatrix);
      });
      gl.drawArrays(gl.TRIANGLES, 0, 3);
    }

大功告成:
image

教练我动不了了

不知道各位看官有没有发现,在矩阵这套线性变化下,我们没办法做平移操作。因为作为原点的o(0,0)不论乘以什么矩阵,结果都还是自己。但是平移操作是日常工作中极其常见的操作,不能平移甚至无法实现拖拽!
难道图形学之路就此gg?

但天无绝人之路,只要零点不是零点我就可以移动它,对于二维平面,我可以把它看作三维世界中一个不过原点的平面,原本的(x,y)变为(x,y,1)
此时就可以实现平移
image

根据上文,我们已经了解的矩阵知识,不难写出
image
而这种通过n+1维实现了n维线性变换外加移动操作的变换,就被称为齐次变换

webgl和齐次变换更配哟~

下面我们继续改造原有的webgl代码!
首先我们还需要加入两个滑块分别控制,图形上下和左右运动

<div id="control">
   ...
    <input type="range" value="0" min="-0.5" max="0.5" step="0.01" id="tranX">
    <input type="range" value="0" min="-.5" max=".5" step="0.01" id="tranY">
  </div>

由于齐次变换将所有的矩阵都升维了,我们需要改造定点着色器。

<script id="vertex-shader-2d" type="notjs">
    ...
    // 将原本二维矩阵定义为三维
    uniform mat3 scaleMatrix; 
    uniform mat3 rotateMatrix;
    uniform mat3 transformMatrix;
    void main() {
      fragColor = vertColor;
      vec3 v = rotateMatrix*scaleMatrix*transformMatrix*vec3(vertPosition,1.0);
      // 由于我们之需要x,y把他们取出即可
      gl_Position = vec4(v.xy,0.0,1.0);
    }
   
  </script>

由于矩阵从二维变为三维,取出的变量也需要重新定义为三维:

...
var trMatrix = gl.getUniformLocation(program,'transformMatrix');
      var scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
      var rotateMatrix = gl.getUniformLocation(program, 'rotateMatrix');
...
// 获取滑块
var tranXNode = document.querySelector("#tranX");
var tranYNode = document.querySelector("#tranY");
// 修改loop函数
...
    loop(gl, rotateMatrix, scaleMatrix,trMatrix);
}
function loop(gl, rotateMatrix, scaleMatrix,trMatrix) {
...
    var myArr = new Float32Array([cos, sin, 0 , -sin, cos, 0,0,0,1]);
    var scaleArr = new Float32Array([scale, 0, 0,0, scale,0,0,0,1]);
    var tranArr = new Float32Array([1,0,0,0,1,0,tranXNode.value,tranYNode.value,1]);
    // console.log(tranXNode.value);
    gl.uniformMatrix3fv(rotateMatrix, false, myArr);
    gl.uniformMatrix3fv(scaleMatrix, false, scaleArr);
    gl.uniformMatrix3fv(trMatrix, false, tranArr);
....

大功告成:
image

下期预告

我想二维的世界,大家也腻了,下篇我们将进入三维世界,并说说光线是如何影响物体的

查看原文

赞 13 收藏 9 评论 1

yeungx 赞了文章 · 2020-12-29

前端面试出场率奇高的18个手写代码

1. 防抖

function debounce(func, ms = 1000) {
  let timer;
  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      func.apply(this, args)
    }, ms)
  }
}

// 测试 const task = () => { console.log('run task') }
const debounceTask = debounce(task, 1000)
window.addEventListener('scroll', debounceTask) 

2. 节流

function throttle(func, ms = 1000) {
  let canRun = true
  return function (...args) {
    if (!canRun) return
    canRun = false
    setTimeout(() => {
      func.apply(this, args)
      canRun = true
    }, ms)
  }
}

// 测试 const task = () => { console.log('run task') }
const throttleTask = throttle(task, 1000)
window.addEventListener('scroll', throttleTask) 

3. new

function myNew(Func, ...args) {
  const instance = {};
  if (Func.prototype) {
    Object.setPrototypeOf(instance, Func.prototype)
  }
  const res = Func.apply(instance, args)
  if (typeof res === "function" || (typeof res === "object" && res !== null)) {
    return res
  }
  return instance
}

// 测试 function Person(name) {
  this.name = name
}
Person.prototype.sayName = function() {
  console.log(`My name is ${this.name}`)
}
const me = myNew(Person, 'Jack')
me.sayName()
console.log(me) 

4. bind

Function.prototype.myBind = function (context = globalThis) {
  const fn = this
  const args = Array.from(arguments).slice(1)
  const newFunc = function () {
    const newArgs = args.concat(...arguments)
    if (this instanceof newFunc) {
      // 通过 new 调用,绑定 this 为实例对象       fn.apply(this, newArgs)
    } else {
      // 通过普通函数形式调用,绑定 context       fn.apply(context, newArgs)
    }
  }
  // 支持 new 调用方式   newFunc.prototype = Object.create(fn.prototype)
  return newFunc
}

// 测试 const me = { name: 'Jack' }
const other = { name: 'Jackson' }
function say() {
  console.log(`My name is ${this.name || 'default'}`);
}
const meSay = say.bind(me)
meSay()
const otherSay = say.bind(other)
otherSay() 

5. call

Function.prototype.myCall = function (context = globalThis) {
  // 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖   const key = Symbol('key')
  context[key] = this
  let args = [].slice.call(arguments, 1)
  let res = context[key](...args)
  delete context[key]
  return res
};

// 测试 const me = { name: 'Jack' }
function say() {
  console.log(`My name is ${this.name || 'default'}`);
}
say.myCall(me) 

6. apply

Function.prototype.myApply = function (context = globalThis) {
  // 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖   const key = Symbol('key')
  context[key] = this
  let res
  if (arguments[1]) {
    res = context[key](...arguments[1])
  } else {
    res = context[key]()
  }
  delete context[key]
  return res
}

// 测试 const me = { name: 'Jack' }
function say() {
  console.log(`My name is ${this.name || 'default'}`);
}
say.myApply(me) 

7. deepCopy

function deepCopy(obj, cache = new WeakMap()) {
  if (!obj instanceof Object) return obj
  // 防止循环引用   if (cache.get(obj)) return cache.get(obj)
  // 支持函数   if (obj instanceof Function) {
    return function () {
      obj.apply(this, arguments)
    }
  }
  // 支持日期   if (obj instanceof Date) return new Date(obj)
  // 支持正则对象   if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
  // 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了 
  // 数组是 key 为数字素银的特殊对象   const res = Array.isArray(obj) ? [] : {}
  // 缓存 copy 的对象,用于处理循环引用的情况   cache.set(obj, res)

  Object.keys(obj).forEach((key) => {
    if (obj[key] instanceof Object) {
      res[key] = deepCopy(obj[key], cache)
    } else {
      res[key] = obj[key]
    }
  });
  return res
}

// 测试 const source = {
  name: 'Jack',
  meta: {
    age: 12,
    birth: new Date('1997-10-10'),
    ary: [1, 2, { a: 1 }],
    say() {
      console.log('Hello');
    }
  }
}
source.source = source
const newObj = deepCopy(source)
console.log(newObj.meta.ary[2] === source.meta.ary[2]); 

8. 事件总线 | 发布订阅模式

class EventEmitter {
  constructor() {
    this.cache = {}
  }

  on(name, fn) {
    if (this.cache[name]) {
      this.cache[name].push(fn)
    } else {
      this.cache[name] = [fn]
    }
  }

  off(name, fn) {
    const tasks = this.cache[name]
    if (tasks) {
      const index = tasks.findIndex((f) => f === fn || f.callback === fn)
      if (index >= 0) {
        tasks.splice(index, 1)
      }
    }
  }

  emit(name) {
    if (this.cache[name]) {
      // 创建副本,如果回调函数内继续注册相同事件,会造成死循环       const tasks = this.cache[name].slice()
      for (let fn of tasks) {
        fn();
      }
    }
  }

  emit(name, once = false) {
    if (this.cache[name]) {
      // 创建副本,如果回调函数内继续注册相同事件,会造成死循环       const tasks = this.cache[name].slice()
      for (let fn of tasks) {
        fn();
      }
      if (once) {
        delete this.cache[name]
      }
    }
  }
}

// 测试 const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }
eventBus.on('task', task1)
eventBus.on('task', task2)

setTimeout(() => {
  eventBus.emit('task')
}, 1000) 

9. 柯里化:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

function curry(func) {
  return function curried(...args) {
    // 关键知识点:function.length 用来获取函数的形参个数     // 补充:arguments.length 获取的是实参个数     if (args.length >= func.length) {
      return func.apply(this, args)
    }
    return function (...args2) {
      return curried.apply(this, args.concat(args2))
    }
  }
}

// 测试 function sum (a, b, c) {
  return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1, 2, 3))
console.log(curriedSum(1)(2,3))
console.log(curriedSum(1)(2)(3)) 

10. es5 实现继承

function create(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

// Parent function Parent(name) {
  this.name = name
}

Parent.prototype.sayName = function () {
  console.log(this.name)
};

// Child function Child(age, name) {
  Parent.call(this, name)
  this.age = age
}
Child.prototype = create(Parent.prototype)
Child.prototype.constructor = Child

Child.prototype.sayAge = function () {
  console.log(this.age)
}

// 测试 const child = new Child(18, 'Jack')
child.sayName()
child.sayAge() 

11. instanceof

function isInstanceOf(instance, klass) {
  let proto = instance.__proto__
  let prototype = klass.prototype
  while (true) {
    if (proto === null) return false
    if (proto === prototype) return true
    proto = proto.__proto__
  }
}

// 测试 class Parent {}
class Child extends Parent {}
const child = new Child()
console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array)) 

12. 异步并发数限制

/**
 * 关键点 * 1. new promise 一经创建,立即执行 * 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法 * 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里 * 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行 * 5. 任务完成后,需要从 doingTasks 中移出 */
function limit(count, array, iterateFunc) {
  const tasks = []
  const doingTasks = []
  let i = 0
  const enqueue = () => {
    if (i === array.length) {
      return Promise.resolve()
    }
    const task = Promise.resolve().then(() => iterateFunc(array[i++]))
    tasks.push(task)
    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
    doingTasks.push(doing)
    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
    return res.then(enqueue)
  };
  return enqueue().then(() => Promise.all(tasks))
}

// test const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
  console.log(res)
}) 

13. 异步串行 | 异步并行

// 字节面试题,实现一个异步加法 function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 500);
}

// 解决方案 // 1. promisify const promiseAdd = (a, b) => new Promise((resolve, reject) => {
  asyncAdd(a, b, (err, res) => {
    if (err) {
      reject(err)
    } else {
      resolve(res)
    }
  })
})

// 2. 串行处理 async function serialSum(...args) {
  return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}

// 3. 并行处理 async function parallelSum(...args) {
  if (args.length === 1) return args[0]
  const tasks = []
  for (let i = 0; i < args.length; i += 2) {
    tasks.push(promiseAdd(args[i], args[i + 1] || 0))
  }
  const results = await Promise.all(tasks)
  return parallelSum(...results)
}

// 测试 (async () => {
  console.log('Running...');
  const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res1)
  const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res2)
  console.log('Done');
})() 

14. vue reactive

// Dep module class Dep {
  static stack = []
  static target = null
  deps = null
  
  constructor() {
    this.deps = new Set()
  }

  depend() {
    if (Dep.target) {
      this.deps.add(Dep.target)
    }
  }

  notify() {
    this.deps.forEach(w => w.update())
  }

  static pushTarget(t) {
    if (this.target) {
      this.stack.push(this.target)
    }
    this.target = t
  }

  static popTarget() {
    this.target = this.stack.pop()
  }
}

// reactive function reactive(o) {
  if (o && typeof o === 'object') {
    Object.keys(o).forEach(k => {
      defineReactive(o, k, o[k])
    })
  }
  return o
}

function defineReactive(obj, k, val) {
  let dep = new Dep()
  Object.defineProperty(obj, k, {
    get() {
      dep.depend()
      return val
    },
    set(newVal) {
      val = newVal
      dep.notify()
    }
  })
  if (val && typeof val === 'object') {
    reactive(val)
  }
}

// watcher class Watcher {
  constructor(effect) {
    this.effect = effect
    this.update()
  }

  update() {
    Dep.pushTarget(this)
    this.value = this.effect()
    Dep.popTarget()
    return this.value
  }
}

// 测试代码 const data = reactive({
  msg: 'aaa'
})

new Watcher(() => {
  console.log('===> effect', data.msg);
})

setTimeout(() => {
  data.msg = 'hello'
}, 1000) 

15. promise

// 建议阅读 [Promises/A+ 标准](https://promisesaplus.com/) class MyPromise {
  constructor(func) {
    this.status = 'pending'
    this.value = null
    this.resolvedTasks = []
    this.rejectedTasks = []
    this._resolve = this._resolve.bind(this)
    this._reject = this._reject.bind(this)
    try {
      func(this._resolve, this._reject)
    } catch (error) {
      this._reject(error)
    }
  }

  _resolve(value) {
    setTimeout(() => {
      this.status = 'fulfilled'
      this.value = value
      this.resolvedTasks.forEach(t => t(value))
    })
  }

  _reject(reason) {
    setTimeout(() => {
      this.status = 'reject'
      this.value = reason
      this.rejectedTasks.forEach(t => t(reason))
    })
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.resolvedTasks.push((value) => {
        try {
          const res = onFulfilled(value)
          if (res instanceof MyPromise) {
            res.then(resolve, reject)
          } else {
            resolve(res)
          }
        } catch (error) {
          reject(error)
        }
      })
      this.rejectedTasks.push((value) => {
        try {
          const res = onRejected(value)
          if (res instanceof MyPromise) {
            res.then(resolve, reject)
          } else {
            reject(res)
          }
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

// 测试 new MyPromise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
}).then((res) => {
    console.log(res);
    return new MyPromise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, 500);
    });
  }).then((res) => {
    console.log(res);
    throw new Error('a error')
  }).catch((err) => {
    console.log('==>', err);
  }) 

16. 数组扁平化

// 方案 1 function recursionFlat(ary = []) {
  const res = []
  ary.forEach(item => {
    if (Array.isArray(item)) {
      res.push(...recursionFlat(item))
    } else {
      res.push(item)
    }
  })
  return res
}
// 方案 2 function reduceFlat(ary = []) {
  return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}

// 测试 const source = [1, 2, [3, 4, [5, 6]], '7']
console.log(recursionFlat(source))
console.log(reduceFlat(source)) 

17. 对象扁平化

function objectFlat(obj = {}) {
  const res = {}
  function flat(item, preKey = '') {
    Object.entries(item).forEach(([key, val]) => {
      const newKey = preKey ? `${preKey}.${key}` : key
      if (val && typeof val === 'object') {
        flat(val, newKey)
      } else {
        res[newKey] = val
      }
    })
  }
  flat(obj)
  return res
}

// 测试 const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source)); 

18. 图片懒加载

// <img data-original="default.png" data-data-original="https://xxxx/real.png"> function isVisible(el) {
  const position = el.getBoundingClientRect()
  const windowHeight = document.documentElement.clientHeight
  // 顶部边缘可见   const topVisible = position.top > 0 && position.top < windowHeight;
  // 底部边缘可见   const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
  return topVisible || bottomVisible;
}

function imageLazyLoad() {
  const images = document.querySelectorAll('img')
  for (let img of images) {
    const realSrc = img.dataset.src
    if (!realSrc) continue
    if (isVisible(img)) {
      img.src = realSrc
      img.dataset.src = ''
    }
  }
}

// 测试 window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// or window.addEventListener('scroll', throttle(imageLazyLoad, 1000)) 
原作者姓名:iboying
原出处:掘金
原文链接:https://juejin.im/post/6873513007037546510

发布于 09-20

查看原文

赞 86 收藏 68 评论 1

yeungx 回答了问题 · 2020-12-27

解决小程序scroll-view 高度不起作用怎么解决?

检查scroll view父级height是否有设置。父级没有height的话,就不好用%单位,可以使用vh 设置scroll view高度

关注 2 回答 1

认证与成就

  • 获得 120 次点赞
  • 获得 23 枚徽章 获得 1 枚金徽章, 获得 4 枚银徽章, 获得 18 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-06-27
个人主页被 2.6k 人浏览