使用Rollup构建企业lib

背景说明

在使用Rollup构建企业代码库的时候,对于其使用方式,尤其是依赖管理存在一些疑虑,加以分析说明。

Tree-Shaking

与传统的 CommonJS 和 AMD 这一类非标准化的解决方案不同,Rollup 使用的是 ES6 版本 Javascript 中的模块标准
目前比较明显的一个问题是,我们所依赖的第三方包,或者现有构建的umd包一般会遇到冗余打包的问题。
tree-shaking就是一种将未实际用到的代码剔除的机制

如果你的 package.json 文件具有 module 属性,像 Rollup 和 webpack 2+ 这类 ES 模块识别工具可以直接导入模块的 ES module 版本。

通常,库会附带一个package.json文件(例如,如果您在 npm 上发布,这是强制性的)。该文件通常会指定一个pkg.main属性 - 如下所示:

这会指示 Browserify 或 Webpack 或 [在此处插入模块捆绑器]在您调用应用程序或库dist/my-package.js时在您的捆绑包中包含内容(以及它所具有的任何依赖项)。require(‘my-package’)

但是对于像 Rollup 这样的 ES2015 感知工具,使用 CommonJS(或通用模块定义)构建并不理想,因为我们无法利用 ES2015 模块特性。因此,假设您已将包编写为 ES2015 模块,您可以在 CommonJS/UMD 构建旁边生成一个 ES2015 模块构建:

依赖管理

针对 esm 与 cjs 类型,因为经过 rollup 编译后代码,还是会被组件库的使用者再次引用,并编译打包,所以我们没有必要把 babel 运行时的代码打包进入,因此这里将 @babel/runtime 的代码也排除了,但是 @babel/runtime 会作为组件库的 dependencies 存在,也就是说,组件库使用者,在使用时一定会下载 @babel/runtime ,从而保证组件库整体的正确
————————————————
版权声明:本文为CSDN博主「mjzhang1993」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mjzhang1993/article/details/112575745

意思是,假如我们的lib依赖qs,但我们并不希望将其打包,因为其他组件库也有极大概率使用(会重复打包吗??),但是在工程中难道我们需要自己安装qs吗,不用,将qs作为dependence依赖但不打包。工程使用lib会安装这个依赖的依赖,且打包的活是工程在干。假如说这个不是qs而是react那么一定还要考虑整个生态的一致性,因此不仅仅是dependence还需要peerDependence

对等依赖的基本概念

Rollup依赖机制

按需引入组件实现

相关资料

https://classic.yarnpkg.com/en/docs/dependency-types/

插件列表

参考资料


slatejs
1
export { Editor, Element, Location, Node, Operation, Path, PathRef, Point, PointRef, Range, RangeRef, Span, Text, Transforms, createEditor };

Editor

createEditor

Transforms


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};

/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};

/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};

/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;

/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;

/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

类型运用存乎一心

比如枚举用在特定场景,且能反向映射

1
2
3
4
5
6
7
8
9
10
11
12
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

事实上,上面的例子会被编译为:

1
2
3
4
5
6
7
8
9
10
11
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

https://developer.51cto.com/art/202102/647082.htm?pc


recoil简介

什么问题

react组件间的状态共享只能通过将state提升至公共祖先实现,带来一个显而易见的问题就是开发效率低

快速入门

如需在组件中使用 Recoil,则可以将 RecoilRoot 放置在父组件的某个位置。将他设为根组件为最佳:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';

function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}

一个 atom 代表一个状态。Atom 可在任意组件中进行读写。读取 atom 值的组件隐式订阅了该 atom,因此任何 atom 的更新都将致使使用对应 atom 的组件重新渲染:

1
2
3
4
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
});

在需要使用的组件中,你应该引入并使用 useRecoilState(),如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function CharacterCounter() {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
}

function TextInput() {
const [text, setText] = useRecoilState(textState);

const onChange = (event) => {
setText(event.target.value);
};

return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
}

selector 代表一个派生状态,派生状态是状态的转换。你可以将派生状态视为将状态传递给以某种方式修改给定状态的纯函数的输出:

1
2
3
4
5
6
7
8
const charCountState = selector({
key: 'charCountState', // unique ID (with respect to other atoms/selectors)
get: ({get}) => {
const text = get(textState);

return text.length;
},
});

我们可以使用 useRecoilValue() 的 hook,来读取 charCountState 的值:

1
2
3
4
5
function CharacterCount() {
const count = useRecoilValue(charCountState);

return <>Character Count: {count}</>;
}

recoil的核心概念

Atom

Atom 是状态的单位。它们可更新也可订阅:

  • 当 atom 被更新,每个被订阅的组件都将使用新值进行重渲染。
  • 它们也可以在运行时创建。可以使用 atom 替代组件内部的 state。
  • 如果多个组件使用相同的 atom,则这些组件共享 atom 的状态。
  • Atom 需要一个唯一的 key 值
  • 要从组件中读取和写入 atom,我们需使用一个名为 useRecoilState 的 hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});

function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}

此时其他组件也可以使用相同的字体大小

1
2
3
4
5
6

function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}

Selector

  • selector 是一个纯函数,入参为 atom 或者其他 selector
  • 当上游 atom 或 selector 更新时,将重新执行 selector 函数
  • 组件可以像 atom 一样订阅 selector,当 selector 发生变化时,重新渲染相关组件。

类似于Vue的computed,Selector 被用于计算基于 state 的派生数据。这使得我们避免了冗余 state,我们只需要将最小粒度存储在Atom中,其他所有内容根据最小粒度进行有效计算

1
2
3
4
5
6
7
8
9
10
11
// 在这个 fontSizeLabelState 示例中,selector 包含一个依赖项:fontSizeState atom. 从概念上讲,fontSizeLabelState selector的行为与纯函数类似,该函数将 fontSizeState 作为输入,并将格式化后的字体大小的 label 值作为返回值。
const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';

return `${fontSize}${unit}`;
},
});

get 属性是用于计算的函数。它可以使用名为 get 的入参来访问 atom 以及其他 selector 的值。每当它访问另一个 atom 或 selector 时,就会创建相应的依赖关系,以便在更新另一个 atom 或 selector 时,该 atom 或 selector 也会被重新计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 可以使用 useRecoilValue() 读取 selector。它使用 atom 或 selector 作为参数,并返回相应的值。我们在这里不使用 useRecoilState(),因为fontSizeLabelState selector 不可写
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);

return (
<>
<div>Current font size: {fontSizeLabel}</div>

<button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
);
}

关于上面selector不可写的问题,参考selector Api

指南

相关链接

官网
中文文档


线性代数的本质

1 向量是什么

线性代数中最基础最根源的部分就是向量

  • 从物理角度看,向量是空间中的箭头,决定一个向量的是长度和方向,可以任意平移
  • 从程序角度,向量是有序的数字列表

    向量加法和数乘贯穿线性代数始终

向量几何意义

  • 二维坐标系中的一个箭头,从原点出发

  • 向量是有序的数字列表,分别用 x,y,z 表示向量在坐标系箭头所在那一个点,一般我们竖着写,用方括号包起来:

[xyz]\left[\begin{matrix} x\\y\\z \end{matrix} \right]

向量加法和向量数乘

向量加法

存在两个向量,为了相加,将第二个向量平移,使得它的起点与第一个向量终点重合,然后画一个从第一个向量起点到第二个向量终点的箭头,就是他们的和。

为什么这样定义???

  • 如果将向量看做在空间中的运动,先从向量 1 方向运动一段距离,再向向量 2 方向移动,总体效果与沿着这两个向量和方向运动一样。当然,顺序换一换也是一样。

  • 从数值计算的角度而言,我们可以这样理解。先沿着 x 轴运动 1,再沿 y 轴运动 2,然后 x 轴运动 3,y 轴-1。

向量数乘

向量长度的伸缩

  • 从数值计算的角度,伸缩等于将 x,y 分量分别相乘

线性代数为数据分析提供了一条将大量数据列表概念化可视化的途径,另一方面,给计算机图形程序员提供了一种语言,通过处理数字来描述并操作空间

2 向量线性组合、张开空间、基

在 xy 坐标系中,有两个特别的向量,单位向量 i,j。

i^j^\hat i \hat j

可以将向量看成是单位向量经过伸缩后求和的结果。因此向量和可以看成是单位向量伸缩求和。

由此引申出一个问题:

我们完全可以选择不同的基向量,获得一个合理的新坐标系,比如两个不垂直的基向量完全可以通过数乘求和获得所有向量

目前我们需要知道的是,当我们用数来描述向量的时候,都依赖我们正在使用的基,两个数乘向量的和被称为这两个向量的线性组合。

av+bwa \vec v + b \vec w

线性这个词是什么意思?

可以这么理解:如果上面公式中 a,b 两个标量固定其中一个,逐渐改变另外一个,所产生的向量的终点最终会绘制出一条直线。

当同时变化两个标量,最终能到达平面内所有的点,即张开平面。(这两个向量同方向例外)

本质上是在问:仅通过向量加法和向量数乘这两种基本运算,能获得可能向量的集合是什么

张成空间

如上所述,两个向量线性组合会张成平面,那么引入第三个向量,就可以张成空间。但是如果第三个向量或者有某两个向量重合,那么就会受困于这一平面,无法张开空间,即这一组向量中至少有一个是多余的,没有对张开空间做出任何贡献。

线性相关

另一方面,如果所有向量都给张成空间添加了新的维度,那么就是线性无关。

如果一个向量是另一个向量的线性组合,那么这两个向量是线性相关的

[2211]\left[\begin{matrix} 2&-2\\-1 &-1 \end{matrix}\right]

综上所述:

空间的一组基的严格定义是这样的:张成该空间的一个线性无关向量的集合

3 矩阵与空间变换

  • 线性变换的概念以及它和矩阵的关系

  • 矩阵的向量乘法

什么是线性变换

变换只是函数一种花哨的说法,输入内容并输出相应结果,之所以用变换的说法,是在暗示一个运动的过程,(即一个输入向量经过变换,移动到输出位置)

空间的而变换可以很复杂,但线性代数限制在一种特殊类型变换上——线性变换

如果一个变换具备以下性质,称之为线性的:

  • 直线在变换后仍是直线
  • 原点必须保持固定

总的来说,应该把线性变换看做是保持网格线平行且等距分布的变换。

那么如何用数值表示这一过程呢(怎么给出一个公式,能拿到变换后的向量坐标)

实际上,你只需要记录两个基向量变换后的位置,其他向量会随之而动

也就是说,一个线性变换可以完全由两组数据决定——基向量变换后的坐标

因此我们将他们放在一个矩阵中,分别表示这两组数据

a[xiyi]+b[xjyj]=[axi+bxjayi+byj]a\left[\begin{matrix} x_i\\y_i \end{matrix} \right]+b\left[\begin{matrix} x_j\\y_j \end{matrix} \right]=\left[\begin{matrix} ax_i+bx_j\\ay_i+by_j \end{matrix} \right]

从某种意义上说,笛卡尔坐标中的向量本身也隐含了一种线性变换:比如(1,1)这个向量

1[10]+1[01]=[11+1010+11]1\left[\begin{matrix} 1\\0 \end{matrix} \right]+1\left[\begin{matrix} 0\\1 \end{matrix} \right]=\left[\begin{matrix} 1*1+1*0\\1*0+1*1 \end{matrix} \right]

再来看看旋转矩阵定义(旋转是最好理解的变换)的含义:

[cosβsinβ0sinβcosβ0001]\left[\begin{matrix} cos\beta& -sin\beta&0\\sin\beta&cos\beta&0 \\0&0&1\end{matrix} \right]

我们直接想象如果逆时针旋转 90 度,可以直接想出原来的 x 轴到了原来 y 轴位置,原来 y 轴到了原来-x 轴位置。单位向量的坐标就变成

[010][100][001]\left[\begin{matrix} 0\\1\\0 \end{matrix}\right] \left[\begin{matrix} -1\\0\\0 \end{matrix}\right] \left[\begin{matrix} 0\\0\\1 \end{matrix}\right]

矩阵向量乘法就是计算线性变换的一种途径

[abcd][xy]=x[ac]+y[bd]=[ax+bycx+dy]\left[\begin{matrix} a&b\\c&d \end{matrix}\right] \left[\begin{matrix} x\\y \end{matrix}\right]=x \left[\begin{matrix} a\\c \end{matrix}\right]+y \left[\begin{matrix} b\\d \end{matrix}\right]=\left[\begin{matrix} ax+by\\cx+dy\end{matrix}\right]

4 复合变换

如何描述:一个变换之后再进行另一个变换

比如旋转之后再剪切的计算过程为:

其复合总体结果等效变换矩阵为

[1110]\left[\begin{matrix} 1&-1\\1&0 \end{matrix}\right]

因此我们可以认为这个新矩阵就是两个变换矩阵的积

我们不必纠结记忆两个矩阵相乘的数学算法,而是明白其几何意义:

我们可以追踪基向量的变化:

然后我们引入通用计算

[abcd][efgh]=[ae+bgaf+bhce+dgcf+dh]\left[\begin{matrix} a&b\\c&d \end{matrix}\right]\left[\begin{matrix} e&f\\g&h \end{matrix}\right]=\left[\begin{matrix} ae+bg&af+bh\\ce+dg&cf+dh \end{matrix}\right]

5 三维空间线性变换

6 行列式

线性变换的结果可能对空间进行了扩大或缩小,如何测量面积变化的比例?

  • 这个比例称为这个变换的行列式(一个矩阵的行列式是=3 说明面积扩大 3 倍,=0 说明压缩到一条线甚至一个点,只要检验行列式是否为 0 就能知道是否将空间压缩到更小维度)

  • 当行列式为负,则改变了空间定向,一种理解是基向量 j 在 i 左边,如果变换后变到右边,则称为改变定向

  • 如果 3 阶行列式为正,符合右手定则;如果为负,符合左手定则

7 逆矩阵、列空间、秩、零空间

求解线性方程组

Ax=vA \vec x=\vec v

2x+5y+3z=34x+0y+8z=01x+3y+0z=22x + 5y+3z=-3 \\ 4x +0y+8z=0 \\ 1x+3y+0z=2

[253408130][xyz]=[302]\left[\begin{matrix} 2&5&3\\4&0&8\\1&3&0 \end{matrix}\right]\left[\begin{matrix} x\\y\\z\end{matrix}\right]=\left[\begin{matrix}-3\\0\\2 \end{matrix}\right]

逆矩阵

A1AA^{-1}A

首先应用 A 变换再应用 A 逆变换,你会回到原始状态

两个变换相继作用在代数上体现为矩阵乘法

所以 A 逆乘以 A 等于一个什么都不做的矩阵(恒等变换)

一旦找到 A 逆(且 A 的行列式不等于 0),然后可以来求解方程:

A1Ax=A1vA^{-1}A \vec x=A^{-1} \vec v

当行列式为 0 并不意味着方程无解

当变换结果为一条线时,也就是说结果是一维的,我们称这个变换的秩=1,代表维度

不管是直线、平面还是空间,所有可能的变换结果的集合称为矩阵的列空间,矩阵的列告诉你基向量变换后的位置,这些基向量张成的空间就是所有可能的变换结果,也就是说列空间就是矩阵列所张成的空间。

秩就是列空间的维数,当秩与列数相等称为满秩

当不满秩,会出现降维,会有一系列向量变换后落在原点,变换后落在原点的向量集合称为零空间。

对于一个方程而言,结果如果恰好是零向量,那么解就是零空间

非方阵

3 行 2 列矩阵:

因为矩阵有两列表明输入空间有两个基向量,有三行表明每一个基向量在变换后都用三个独立坐标描述;

2 行 3 列矩阵:

3 列表明原始空间有 3 个基向量,两行表明这 3 个基向量在变换后都仅用两个坐标描述,一定落在二维空间,因此这是三维到二维空间的变换。

8 点积

[abc].[xyz]=ax+by+cz\left[\begin{matrix} a\\b\\c \end{matrix}\right] . \left[\begin{matrix} x\\y\\z\end{matrix}\right]=ax+by+cz

几何意义是

当两个向量相互垂直,点积为 0;方向大致相同,点积为正;点积是不受顺序影响的,这是因为,当两个向量对称,明显可以证明双方投影长度一致,所以结果不受顺序影响,当你放大其中一个向量(要么是投影随相同倍数放大,要么投影不变,向量成倍),显然结果不受顺序影响。

叉乘

真正的叉积是通过两个三维向量生成一个新的三维向量

v×w=p\vec v \times \vec w=\vec p

[v1v2v3]×[w1w2w31]=det([i^v1w1j^v2w2k^v3w3])\left[\begin{matrix} v_{1}\\v_{2}\\v_{3} \end{matrix}\right] \times \left[\begin{matrix} w_{1}\\w_{2}\\w_{31} \end{matrix}\right]=det(\left[\begin{matrix} \hat i & v_{1} & w_{1} \\\hat j & v_{2} & w_{2}\\\hat k & v_{3} & w_{3} \end{matrix}\right])

i^(v2w3v3w2)+j^(v3w1v1w3)+k^(v1w2v2w1)\hat i(v_{2}w_{3}-v_{3}w_{2})+\hat j(v_{3}w_{1}-v_{1}w_{3})+\hat k(v_{1}w_{2}-v_{2}w_{1})

  • 这个向量的长度就是平行四边形的面积
  • 而方向则与平行四边形所在的面垂直
  • 使用右手定则来判断其方向

前端3D应用开发—— ThreeJS入门与3D应用开发简介

前言

计算机 3D 图形的历史可以追溯到 20 世纪 60 年代,几乎和计算机本身的历史一样长。它被广泛应用于工程、教育、培训、建筑、金融、销售、市场、博彩、娱乐等各个领域。曾经 ,3D 图形只能用计算机软件渲染。如今,所有的计算机和移动设备都搭载了 3D 图形处理硬件,普通智能手机甚至有着比五年前的专业图形工作站更为优秀的图形处理能力。更重要的是现代 Web 浏览器也支持了 3D 渲染,相比昂贵的 3D 专用渲染 件,浏览器显然更普遍,更易于获取,并且是免费的。

HTML5 新型视觉媒介的核心是一系列先进的图形技术:WebGL、css3 3d,canvas。在接触 WebGL 领域的时候,积累了不少学习资料,分别从前端可视化、游戏、3D 应用等不同方向展示 WebGL 的具体应用。他们有的自底向上,从计算机图形学开始到具体需求实现;有的自顶向下,从需求出发,一步步实现一个完整的应用。无论哪种方式,这一领域都需要漫长的耕耘,无法满足快速起步的目的。

目前这一系列主标题是前端 3D 应用开发,主要探讨如何从 0 到 1 落地一个 3D 项目。由于发现介绍原始的 WebGL 开发难度过大,内容枯燥,使用中心向两端的方式,根据实际情况向前或向后灵活展开,更为合适。当前安排如下:

  • 介绍 Three.js 及快速入门案例
  • Three.js 简介

ThreeJS 入门

以下内容整理自《Three.js 开发指南》

获取源码的方式

1
git clone https://github.com/josdirksen/learning-threejs-third

代码需要运行在 Web 服务器,可以自行处理,也准备了一个在线资源

第一行代码

查看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// once everything is loaded, we run our Three.js stuff.
function init() {
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();

// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

// create a render and set the size
var renderer = new THREE.WebGLRenderer();
renderer.setClearColorHex();
renderer.setClearColor(new THREE.Color(0xeeeeee));
renderer.setSize(window.innerWidth, window.innerHeight);

// show axes in the screen
var axes = new THREE.AxisHelper(20);
scene.add(axes);

// create the ground plane
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshBasicMaterial({ color: 0xcccccc });
var plane = new THREE.Mesh(planeGeometry, planeMaterial);

// rotate and position the plane
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;

// add the plane to the scene
scene.add(plane);

// create a cube
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

// position the cube
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;

// add the cube to the scene
scene.add(cube);

// create a sphere
var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x7777ff, wireframe: true });
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);

// position the sphere
sphere.position.x = 20;
sphere.position.y = 4;
sphere.position.z = 2;

// add the sphere to the scene
scene.add(sphere);

// position and point the camera to the center of the scene
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);

// add the output of the renderer to the html element
document.getElementById('WebGL-output').appendChild(renderer.domElement);

// render the scene
renderer.render(scene, camera);
}
window.onload = init;

代码中首先定义了场景、摄像机和渲染器对象。场景是一个容器,示例中的立方体和圆球都会添加到场景中;摄像机决定了能够在场景中看到什么;渲染器会基于摄像机角度来计算渲染成什么样子。后面我们有机会了解这些概念是怎么一回事

第二部分添加了轴和平面。我们创建了坐标轴对象并设置粗细 20,最后调用 scene.add 方法将轴添加到场景。平面创建分两步进行,首先定义大小,然后通过创建材质来设置外观,然后将大小和外观组合进 Mesh 对象并赋予给平面变量。材质的概念也是稍后要介绍的

以同样的方式加入方块和球,设置 wireframe: true 物体不会渲染为实体。最后一部分设置相机位置和 lookAt 方向。接下去还会使用光照、阴影、材质和动画来美化这个场景

添加材质、光源和阴影

基础材质不会对光源有任何反应,需要改为 MeshLambertMaterial

1
2
3
4
var planeGeometry = new THREE.PlaneGeometry(60, 20);
var planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
1
2
3
4
5
// add spotlight for the shadows
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
scene.add(spotLight);

因为渲染阴影比较耗费性能,所以默认关闭,需要做如下修改:

1
2
3
4
5
// create a render and set the size
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0xeeeeee, 1.0));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;

此外还需要明确指定哪个物体投射阴影,哪个接受阴影。

1
2
3
4
5
plane.receiveShadow = true;
//...
cube.castShadow = true;
//...
sphere.castShadow = true;

点击查看源码

Three.js 简介

Three.js 的作者 Ricardo Cabello Miguel 或者称呼为 Mr.doob。Three.js 一开始是用 ActionScript 编写的,2010 年第一个使用 SVG 和 2DCanvas 的版本诞生,几个月后 WebGL 发布了,便开始移植到 WebGL 上进行继续开发,从那时开始,Three.js 逐渐强大 完善,并最终成为创建 WebGL 3D 应用的最流行选择。

  • Three.js 隐藏了 WebGL 渲染中的底层细节。Three.js 简化了 WebGL API 的细节 将 3D 场景表示为网格、材质、灯光(即图形开发者常用的各种对象类型)。

  • Three.js 非常强大。Three.js 不仅仅是 WebGL 的一个封装,它内置了许多可用千游戏开发、动画、演示、数据可视化、建模工具应用的非常有用的模型对象,还提供了用千特殊效果的后渲染机制。除了它本身的能力,Three.js 还拥有丰富的示例,你可以在你的项目中使用这些示例的代码。

  • Three.js 非常易用。Three.js 的 API 基千友好和易学的理念设计。随库提供的许多示例能够帮助你更好地入门。

  • Three.js 运行速度很快。Three.js 采用了 3D 图形的最佳大践,既保证了高性能,又不因此牺牲可用性。

  • Three.js 非常稳定。它包含完备的错误检查、异常和控制台警告,让开发者可以准确地跟踪程序问题。

  • Three.js 支持交互。WebGL 不具备原生的选中支持,这意味着你无法获取到鼠标经过了当前场景中的哪个物体。Three.js 提供了对选中的支持,使得为应用添加交互变得更加简单。

  • Three.js 支持数学计算。Three.js 包含强大易用的 3D 数学运算对象,如矩阵、映射和向匿。

  • Three.js 内置第三方文件格式支持。你可以利用 Three.js 提供的接口载入以文本格式存储的、用流行的建模工具导出的 3D 模型。Three.js 也定义了自己专属的 JSON 和二进制 3D 模型文件格式。

  • Three.js 是面向对象的。开发者基千设计优良的 JavaScript 对象编写代码,而不是仅仅去调用一些函数。

  • Three.js 是可扩展的。无论你希望为 Three.js 添加一些特性,还是定义自己的个性化 Three.js,都是非常简单的事。如果现有的数据格式支持无法满足你的需求,那么你可以为特定的数据格式编写相应的插件。

  • Three.js 包含 2D Canvas、SVG、CSS 的渲染引擎。尽管 WebGL 已经非常流行,但它并未被所有的浏览器支持,这表示对某些应用来说,WebGL 并不是最好的选择。幸好 Three.js 还可以使用 2D Canvas 和 SVG 元素来渲染大部分的内容。当运行环境不支持 3D Canvas 的时候,这为你的代码提供了一个优雅的降级方案。

需要提醒大家的是,Three.js 也有一些不擅长的事情。比方说,Three.js 并不是一个游戏引擎。它缺乏一些游戏引擎所需的常用特性,如公告牌、头像、有限状态机以及物理引擎。 Three.js 也没有编写多人联机游戏所需的内置网络支持。你需要基千 Three.js 自行构建它,或是整合其他专用的代码库。同时 Three.js 也不是一个应用框架,它不具备一个框架应有的安装、卸载、事件处理和运行循环机制。

另外一个简单入门案例

WebGL 入门

WebGL 原生 API 是一种非常低等级的接口,就好像一个只有加法的计算器,尽管有许多诸如 Three.js 之类的图形库,如果想要掌控 WebGL 编程,依然需要坚持学习更多底层知识,因此自底向上的学习方式,可能会让你经历他人突飞猛进而自己仍然在画三角形的挫败感。不要放弃学习基础知识,也要灵活尝试成熟的框架。

WebGL 的优势体现在 Web 和 3D

什么是 3D

3D 计算机图形(相对 2D 计算机图形而言)是使用三个维度来表示几何数据(通常使用笛卡儿坐标系)并将其存储在计算机中,用于计算和绘制成屏幕上 2D 图像的一类图形。这类图形可被存储起来随时浏览,也可用于实时显示。

WebGL 坐标系统

默认情况下 WebGL 使用右手坐标系。

右手坐标系

WebGL 对于使用左手还是右手坐标系这个问题是中立的,那为什么那么多书籍教程都将 WebGL 坐标系描述为右手呢?这是因为使用右手坐标系是个传统,当你开发自己的程序需要先确定使用的坐标系统,然后不再改变。这一点对于各类图形库同样成立,早期图形库大部分采用了右手坐标,时至今日右手坐标系已经成为了传统,以致成了 GL 图形语言的一部分。

那为什么会有疑问?如果所有人都接受同一个传统,就没问题了,但在特定场景下需要让 WebGL 选择一种坐标系完成运算,就需要知道它的默认设定,而默认设定并不总是右手坐标系!关于 WebGL 默认行为探索,比如隐藏面消除和裁剪坐标系统,可以参考**《WebGL 编程指南》**附录 D,目前,你可以认为 WebGL 就是右手坐标系。

变换矩阵

对于简单的变换,可以使用数学表达式来实现。但当情况变得复杂,你会发现表达式运算实际相当繁琐。比如旋转后平移,就需要两个表达式叠加,获得一个新等式,然后在顶点着色器中实现。也就是说每次要实现一个新的着色器,这很不科学。我们可以使用变换矩阵来完成这项工作。

向量的点乘和叉乘

相关资料 B 站

线性变换的本质

将矩阵和矢量相乘就获得一个新的矢量

[xyz]=[abcdefghi]×[xyz]\left[\begin{matrix} x^{'}\\y^{'}\\z^{'} \end{matrix} \right]=\left[ \begin{matrix} a & b & c\\ d & e&f\\ g & h & i \end{matrix} \right] \times \left[\begin{matrix} x\\y\\z \end{matrix} \right]

注意只有矩阵的列数与矢量的行数相等时,才可以将两者相乘

x=ax+by=czx^{'}= ax+by=cz

y=dx+ey+fzy^{'}= dx+ey+fz

z=gx+hy+izz^{'}= gx+hy+iz

变换矩阵:旋转

x=r(cosαcosβsinαsinβ)x^{'}= r(cos \alpha cos \beta-sin \alpha sin\beta)

y=r(sinαcosβcosαsinβ)y^{'}= r(sin \alpha cos \beta-cos \alpha sin\beta)

将 3.2 代入消除,最终可得

x=xcosβysinβx^{'}=xcos\beta -y sin\beta

y=xsinβ+ycosβy^{'}=xsin\beta +y cos\beta

二维旋转矩阵直观的理解方式是:三个基向量 i,j,k :[1,0,0],[0,1,0],[0,0,1],因为线性变换的本质是基向量的变化,在二维平面中 z 轴方向不发生变化,而新的基向量的坐标其实是在原始坐标系的投影,我们直接想象如果逆时针旋转 90 度,可以直接想出原来的 x 轴到了原来 y 轴位置,原来 y 轴到了原来-x 轴位置。单位向量的坐标就是[0,1,0],[-1,0,0],[0,0,1],是不是和上面矩阵值一致呢

相机、透视、视口、投影

3D 系统通常使用相机(camera)概念来描述用户查看渲染好场景的观察点。相机定义了用户相对于场景位置朝向,它具备现实中相机的属性,如视野的尺寸,它定义了透视(perspective,即远处物体看起来小)。相机通常用一对矩阵表示。第一个矩阵定义相机位置和方向,类似变换矩阵。第二个矩阵表示 3D 到 2D 绘制坐标转换,称为投影矩阵(projection matrix)。这些细节一般工具都封装好了,只需要对准、拍摄。

下图描述了相机、视口、透视的概念。眼睛代表相机位置,x 轴表示相机指向,两个矩形代表近剪裁平面和远剪裁平面。两个平面间定义了 3D 空间子集的范围,通称视锥体或视见体。只有位于视锥体中的物体才会真正被渲染。

谈谈对透视矩阵的理解:矩阵是一系列坐标转换,一个 3D 坐标系中的物体,我们以上帝视角来看,是绝对的坐标,不管远近,然而,因为视觉上的需求,需要做“透视”,即远近大小看起来不一致(想象一下火车头和火车尾)。即将绝对坐标转换为视觉坐标。

WebGL 应用示例

WebGL 就是个绘图库,类似 Canvas 2D 那样,是另外一种 Canvas。为了在页面中渲染 WebGL,应当执行以下步骤:

  • 创建一个 Canvas 元素
  • 获取 Canvas 上下文
  • 初始化视口
  • 创建一个或多个待渲染数据的缓冲
  • 创建一个或多个定义定点缓冲到屏幕空间转化规则的矩阵
  • 创建实现绘制算法的着色器
  • 使用参数初始化着色器
  • 绘制

Canvas 元素及绘图上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function initWebGL(canvas) {

var gl = null;
var msg = "Your browser does not support WebGL, " +
"or it is not enabled by default.";
try
{
gl = canvas.getContext("experimental-webgl");
}
catch (e)
{
msg = "Error creating WebGL Context!: " + e.toString();
}

if (!gl)
{
alert(msg);
throw new Error(msg);
}

return gl;
}

视口

当你从 canvas 获取 WebGL 绘图上下文是,需要定义一个绘制区域的矩形边界。在 WebGL 中,这个矩形边界被称为视口。

1
2
3
function initViewport(gl, canvas) {
gl.viewport(0, 0, canvas.width, canvas.height);
}

缓冲、缓冲数组和类型化数组

WebGL 基于图元(primitive)进行绘制。图元是指不同类型的基本几何图形。WebGL 的图元包括三角形、线、点。三角形是最常用的,通常使用两种形式存储:以数组形式存储的三角形和三角形带。图元以数组形式存储数据,这个数组称为缓冲(buffer),待绘制的顶点数据在缓冲中被定义。下面展示如何创建单位(1X1)正方形顶点缓冲,我们使用三角形带,一个三角形带定义了一组连续的三角形,前三个顶点表示第一个三角形,后续三角形都与它前一个共用两个顶点:

1
2
3
4
5
6
7
8
9
10
// Create the vertex data for a square to be drawn
function createSquare(gl) {
var vertexBuffer;
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
var verts = [0.5, 0.5, 0.0, -0.5, 0.5, 0.0, 0.5, -0.5, 0.0, -0.5, -0.5, 0.0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
var square = { buffer: vertexBuffer, vertSize: 3, nVerts: 4, primtype: gl.TRIANGLE_STRIP };
return square;
}

Float32Array 是因为 WebGL 才被引入浏览器的,类型化数组可以普遍使用,访问速度更快,耗费的内存更小。

矩阵

在绘制之前,首先要创建一对矩阵。一个矩阵用于定义正方形在 3D 坐标系统中的位置(相对于相机),这个矩阵同时包含相机位置和模型位置,称为模型-视图矩阵。在例子中,沿着 z 轴负方向对正方形平移(当相机位置被移动,程序实际进行的处理是根据当前相机位置对整个场景进行平移)。第二个矩阵是投影矩阵,着色器使用它执行 3D-2D 坐标转换。示例中定义了一个 45 度视野的透视相机。我们用glMatrix 开源库来进行矩阵运算,现在我们放弃关心透视矩阵推导过程及背后的观察原理。

1
2
3
4
5
6
7
8
9
10
11
12
var projectionMatrix, modelViewMatrix;

function initMatrices(canvas) {
// Create a model view matrix with object at 0, 0, -3.333
modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -3.333]);

// Create a project matrix with 45 degree field of view
projectionMatrix = mat4.create();
// perspective(out, fovy, aspect, near, far)
mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 1, 10000);
}

透视矩阵推导

透视矩阵

着色器

一个着色器可以应用于多个对象,因此实际应用中,整个场景通常使用一个着色器,通过设置不同参数,在不同几何上复用。着色器通常有两部分:顶点着色器(vertex shader)和片元着色器(fragment shader)。顶点着色器负责将物体坐标转换为 2D 显示区坐标,片元着色器负责转换好的顶点最终颜色输出,基于颜色、纹理、光照、材质等数值输入。

相关推荐

canvas API

WebGL API

SVG

3Blue1BrownB 站

华中科技大学-计算机图形学

奇舞团月影可视化课程源码

《交互式计算机图形学——基于 WebGL 的自顶向下方法(第七版)

本站收集在线示例

参考资料

跟月影学可视化

WebGL 编程指南

Three.js 开发指南

Introduction to Computer Graphics A Practical Learning Approach


1.1.4 校验码
Here's something encrypted, password is required to continue reading.
Read more
hexo自动部署+gitlab

前期工作

之前入手了一台miniPC安装好了Ubuntu,并通过frp内网穿透部署好了gitlab。现在决定重拾荒废的Blog,并做成自动部署。

本文想达到的目的是在任意一台主机编写Blog,并能够做到提交后自动部署。

当前hexo版本5.0以上(安装主题的方式有所不同)

条件

  • 云服务器,公网IP+域名
  • 服务器自行安装好Nginx+git
  • 本地安装好node

开始

本地安装hexo及初始化

1
2
3
4
5
npm install -g hexo-cli

hexo init myblog
cd myblog
npm install

创建服务端账户

一般我们需要专门为服务创建不同用户及用户组,以便做好权限管理。
当前是以root用户登录服务器的

1
2
3
useradd blog
passwd blog #设置用户密码
usermod -a -G root blog #设置组

为了方便,直接将blog用户加到了root组里面

在服务器创建空的git仓库

届时会通过git的钩子方法自动将文件复制到静态文件夹

1
2
3
4
5
mkdir /home/blog/repo/
chown -R blog:root /home/blog/repo/
chmod -R 755 /home/blog/repo/
cd /home/blog/repo/
git init --bare hexo_static.git

既然创建了专用的用户账号,就把相应的资源放在用户目录下/home/blog/就是用户home目录,我们将文件都放到home目录的repo目录中

Read more
vue实现input强制输入Number类型

需求

如果想要实现input输入Number类型,又不想用难看的type='number’怎么处理

1
2
<el-input style="width:200px" :maxlength="4" @keyup.native="isNumber(form,'recruitsCount')" v-model.number="form.recruitsCount" placeholder="请输入数字,不能为空"></el-input>

1
2
3
4
5
6
7
8
9
isNumber(obj,key) {
let isnumber = new RegExp('^[0-9]\\d*$')
if (!isnumber.test(obj[key])) {
obj[key] = null
} else {
return true
}
},

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
</head>

<body>
<div id="app">
<input style="width:200px" :maxlength="4" @keyup="isNumber(form,'count')" v-model.number="form.count" placeholder="请输入数字,不能为空"></input>
</div>
</body>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
form: {
count: null
}

},
methods: {
isNumber(obj, key) {
let isnumber = new RegExp('^[0-9]\\d*$')
if (!isnumber.test(obj[key])) {
obj[key] = null
} else {
return true
}
}
}
})
</script>

</html>



说明

  • 配合.number修饰符实现纯数字输入
  • .nmber的特点是将字符串转为num型,如果转换结果是NaN,则原样输出。
  • 因此如果第一个字符输入非数字,那么就会变成字符串,根据强制类型转换规则,首字母是数字才能转换成功,如1f这种就会转换为1,因此就出现预想中的输入非数字转换为数字的效果。
  • 因此关键是第一个字符不能为非数字,配合keyUp事件,检测首字母是否是数字即可,检查全部也无所谓,因为keyup事件发生时,input事件未发生,input又经过类型转换,因此能完美运行

编程日志(08)—vue实现plupload分片上传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<!-- plupload封装 -->
<template>
<div class="supload">
<div class="clearfix list-picture" v-if="fileList.length>0&&listType=='picture'">
<div v-for="ff,index in fileList" class="pic-bx small">
<img :src="ff.showUrl">
<div class="cl-bx">
<i class="el-icon-close" @click="deleteFile(ff,index)"></i>
</div>
</div>
</div>
<div class="plupload clearfix" v-show="fileList.length<maxFile">
<template v-if="listType=='text'">
<div class="text_trigger">
<div :id="trigger_id" >
<i class="el-icon-document" ></i>
<span>{{triggerText}}(小于{{maxSize}}Mb)</span>
</div>
</div>
</template>
<template v-if="listType=='picture'">
<div class="fm-up" :id="trigger_id">
<div class="cc">
<i class="el-icon-circle-plus-outline"></i>
<p>点击选择</p>
<span>支持jpg/png格式</span>
<span>560px×400px</span>

</div>
</div>
</template>
</div>
<div class="pgs" v-if="progress>0&&listType=='text'">
<el-progress :text-inside="true" :stroke-width="20" :percentage="progress" status="success"></el-progress>
</div>

<div class="list-text" v-if="listType=='text'">
<template v-for="ff,index in fileList">
<div class="clearfix item-text">
<!-- <div class="progress" :style="{'width':ff.progress+'%'}"></div> -->
<div class="title">{{ff.name}}</div>
<div class="close" @click="deleteFile(ff,index)">删除</div>
</div>

</template>
</div>

</div>
</template>

<script>
/**
* 强制multi_selection为false使其只能逐个上传
*
*/
/**
* tip:
* 为了操作方便fileList以数组形式传入props,直接操作fileList可以实现数据双向绑定
* 并不建议这样去做,父子组件之间尽量保持单向数据流
* 因此可以暂不向外提供文件删除上传的回调入口
*/
/*
xlsx等文档可以在拓展过滤为zip的情况下可选的bug是因为xlsx本质是个压缩包,将xlsx拓展名改成zip再看看
*/
import plupload from 'plupload'
import {guid,getExtName} from '../../lib/util.js'

import config from '../../config/config.js'
export default {
data() {
return {
trigger_id:null, //触发按钮的ID属性
plup:null, // plupload实例对象
progress: 0, // 单文件上传进度
option: null
};
},
props: {
accept: {
type: String,
default: 'image/png,image/jpeg,audio/mp3'
},
// 支持的拓展名以逗号隔开jpg,gif,png,bmp,mp4
extensions:{
type: String,
default: 'mp4'
},
maxFile: {
type: Number,
default: 1
},
maxSize: {
type: Number,
default: 512 //mb
},
// 文件列表
fileList: {
default(){
return []
}
},
// text,picture 以picture形式呈现时,如果超过可上传数量,上传界面将消失
listType:{
default: 'text'
},
params:{
type:Object,
required:true
},
triggerText:{
default:'选择文件'
}
},
computed:{

},
components: {

},

methods: {
transGetUrl(url) {
return config.imgHost + url
},


deleteFile(ff,index){
this.fileList.splice(index,1)
},

},
watch:{
extensions(ne,olg){
this.option.filters.mime_types=[{
title: 'files', extensions: ne
}]

this.plup.destroy()
this.plup = new plupload.Uploader(this.option);
this.plup.init()

}

},
mounted(){
const $this = this;
this.option = {
runtimes: 'html5,flash,silverlight,html4',
url: config.downloadHost+'upload/pluploadUpload',
//url: '/preschool/upload/pluploadUpload',
browse_button: this.trigger_id,
chunk_size: '2048kb',
multi_selection:false,
filters: {
mime_types: [{ title: 'files', extensions: this.extensions }],
max_file_size: this.maxSize+'mb',
prevent_duplicates: true
},
multipart:true,
multipart_params:this.params,
init: {
FilesAdded: function(up, files) {
up.start();
},
UploadProgress: function(up, file) {
$this.progress = file.percent;
},
BeforeUpload: function(up,file) {},
FileUploaded: function(uploader,file,res){
// response,responseHeaders,status
if (res.status==200){
let result = JSON.parse(res.response);
if (result.code==0){
setTimeout(function(){
$this.progress = 0;
$this.fileList.push({
name:file.name,
// url: result.data.replace(/(.*)(pluploadDir)/g,config.imgHost),
url: result.data,
showUrl: $this.transGetUrl(result.data),
type: getExtName(result.data)
});
},500);
}
} else {
}
},
Error:function(up,err){},
Refresh: function(up){},
Destroy(up){},
OptionChanged:function(up,option_name,new_value,old_value){}
// Error: this.error
}
}
this.plup = new plupload.Uploader(this.option);
this.plup.init();
},
beforeMount(){
this.trigger_id = guid();
}
};
/**
* 额外参数传递:
* 1.设置multipart为true
* 为true时将以multipart/form-data的形式来上传文件,为false时则以二进制的格式来上传文件。
* 2. 设置multipart_params:{userId:1024}
* 方式二
* 设置init的BeforeUpload(up,file){up.setOption("multipart_params",{userId:1024})}
* 也可以up.setOption({'multipart_params':{userId:1030}});这样可以方便设置多个参数
* 像 up.setOption({'multipart':true,'multipart_params':{userId:1032}})
* 在某些情况下可能更方便
*/
</script>

调用方式

1
2
3
<plup-vue :file-list="fileListForAttach" list-type="text" :params="extendParamsForUploadComponent" :extensions="format" :maxSize="1024">
</plup-vue>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*上传组件*/
.supload{
line-height: 30px;
.pgs{
line-height:20px;width:600px;
}
.list-text{
line-height:20px;width:600px;
.item-text{
line-height: 30px;
height: 30px;
background: #ededed;
position: relative;
margin: 5px auto;
.progress{
position: absolute;
height: 100%;
width: 1%;
background: green;
top: 0;left: 0;
opacity: 0.2;
transition: width 0.5s;
-moz-transition: width 0.5s;
-webkit-transition: width 0.5s;
-o-transition: width 0.5s;

}
.title{
float: left;height: inherit;line-height: inherit;padding: 0 10px;
width: 500px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.close{
float: right;height: inherit;
line-height: inherit;padding: 0 10px;
cursor: pointer;
color:$colorRed
}
}
}
.list-picture {

.pic-bx{
float: left;
margin: 10px 10px 10px 0;
border:1px dashed #ededed;
img{width: 100%; }
position: relative;
}
.cl-bx{
width: 100%;height: 25px;background: rgba(1,1,1,0.5);
position: absolute;right: 0;top: 0;
text-align: right;
line-height: 25px;
i{
font-size: 15px;
color:#fff;
margin-right: 3px;
cursor: pointer;
}
}
.small{
width: 240px;height: 180px;overflow: hidden;
border-radius: 4px
}
}
.plupload{
.text_trigger{
display: inline-block;
position: relative;
color:$colorBlue;
cursor: pointer;
}

}

}
.fm-up{
width: 240px;height: 180px;
text-align: center;
cursor: pointer;
// border: 1px solid #03A9F4;
.cc{
height: 100px;width: 200px;
line-height: 20px;
margin: auto;
margin-top: 40px;
}
color:#dcdcdc;
border: 1px solid #dcdcdc;
i{font-size: 25px;}
border-radius: 4px;
p{line-height: 20px;}
span{font-size: 12px;line-height: 20px;}
}

编程日志(03)—圣杯布局、双飞翼布局

双飞翼布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>
<style>
* {
margin: 0;
padding: 0
}

body {
min-width: 550px;
font-weight: bold;
font-size: 20px;
}

#header,
#footer {
background: rgba(29, 27, 27, 0.726);
text-align: center;
height: 60px;
line-height: 60px;
}

#container {
overflow: hidden;
}

.column {
text-align: center;
}

#left,
#right,
#center {
float: left;
/*三列高度撑开对齐,关键步*/
padding-bottom: 10000px;
margin-bottom: -10000px;
}

#center {
width: 100%;
background: rgb(206, 201, 201);
}

#left {
width: 200px;
margin-left: -100%;
background: rgba(95, 179, 235, 0.972);
}

#right {
width: 150px;
margin-left: -150px;
background: rgb(231, 105, 2);
}

.content {
margin: 0 150px 0 200px;
height: 400px;
}
</style>

<body>
<div id="header">#header</div>
<div id="container">
<div id="center" class="column">
<div class="content">
#center
</div>
</div>
<div id="left" class="column">#left</div>
<div id="right" class="column">#right</div>
</div>
<div id="footer">#footer</div>
</body>

</html>

image

说明

  • 三列布局,中间宽度自适应,两边定宽
  • 中间栏要在浏览器中优先展示渲染
  • 允许任意列的高度最高

查看源码及更多