mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-23 20:02:20 +00:00
19.
This commit is contained in:
parent
39306cd144
commit
70e1e2aa82
120
19.md
120
19.md
@ -38,9 +38,9 @@
|
||||
|
||||
这种方法有许多变种,每个变种都有自己的好处和问题,但它们的中心思想是一样的:状态变化应该通过明确定义的渠道,而不是遍布整个地方。
|
||||
|
||||
我们的组件将是与界面一致的类。 他们的构造函数被赋予一个状态,它可能是整个应用状态,或者如果它不需要访问所有东西,是一些较小的值,并使用它构建一个`dom`属性,也就是表示组件的 DOM。 大多数构造函数还会接受一些其他值,这些值不会随着时间而改变,例如它们可用于分派操作的函数。
|
||||
我们的组件将是与界面一致的类。 他们的构造器被赋予一个状态,它可能是整个应用状态,或者如果它不需要访问所有东西,是一些较小的值,并使用它构建一个`dom`属性,也就是表示组件的 DOM。 大多数构造器还会接受一些其他值,这些值不会随着时间而改变,例如它们可用于分派操作的函数。
|
||||
|
||||
每个组件都有一个`setState`方法,用于将其同步到新的状态值。 该方法接受一个参数,该参数的类型与构造函数的第一个参数的类型相同。
|
||||
每个组件都有一个`setState`方法,用于将其同步到新的状态值。 该方法接受一个参数,该参数的类型与构造器的第一个参数的类型相同。
|
||||
|
||||
## 状态
|
||||
|
||||
@ -72,7 +72,7 @@ class Picture {
|
||||
|
||||
我们希望能够将图片当做不变的值,我们将在本章后面回顾其原因。 但是我们有时也需要一次更新大量像素。 为此,该类有`draw`方法,接受更新后的像素(具有`x`,`y`和`color`属性的对象)的数组,并创建一个覆盖这些像素的新图像。 此方法使用不带参数的`slice`来复制整个像素数组 - 切片的起始位置默认为 0,结束位置为数组的长度。
|
||||
|
||||
`empty `方法使用我们以前没有见过的两个数组功能。 可以使用数字调用`Array`构造函数来创建给定长度的空数组。 然后`fill`方法可以用于使用给定值填充数组。 这些用于创建一个数组,所有像素具有相同颜色。
|
||||
`empty `方法使用我们以前没有见过的两个数组功能。 可以使用数字调用`Array`构造器来创建给定长度的空数组。 然后`fill`方法可以用于使用给定值填充数组。 这些用于创建一个数组,所有像素具有相同颜色。
|
||||
|
||||
颜色存储为字符串,包含传统 CSS 颜色代码 - 一个井号(`#`),后跟六个十六进制数字,两个用于红色分量,两个用于绿色分量,两个用于蓝色分量。这是一种有点神秘而不方便的颜色编写方法,但它是 HTML 颜色输入字段使用的格式,并且可以在`canva`s绘图上下文的`fillColor`属性中使用,所以对于我们在程序中使用颜色的方式,它足够实用。
|
||||
|
||||
@ -218,3 +218,117 @@ PictureCanvas.prototype.touch = function(startEvent,
|
||||
```
|
||||
|
||||
对于触摸事件,`clientX`和`clientY`不能直接在事件对象上使用,但我们可以在`touches`属性中使用第一个触摸对象的坐标。
|
||||
|
||||
## 应用
|
||||
|
||||
为了能够逐步构建应用,我们将主要组件实现为画布周围的外壳,以及一组动态工具和控件,我们将其传递给其构造器。
|
||||
|
||||
控件是出现在图片下方的界面元素。 它们为组件构造器的数组而提供。
|
||||
|
||||
工具是绘制像素或填充区域的东西。 该应用将一组可用工具显示为`<select>`字段。 当前选择的工具决定了,当用户使用指针设备与图片交互时,发生的事情。 它们作为一个对象而提供,该对象将出现在下拉字段中的名称,映射到实现这些工具的函数。 这个函数接受图片位置,当前应用状态和`dispatch`函数作为参数。 它们可能会返回一个移动处理器,当指针移动到另一个像素时,使用新位置和当前状态调用该函数。
|
||||
|
||||
```js
|
||||
class PixelEditor {
|
||||
constructor(state, config) {
|
||||
let {tools, controls, dispatch} = config;
|
||||
this.state = state;
|
||||
|
||||
this.canvas = new PictureCanvas(state.picture, pos => {
|
||||
let tool = tools[this.state.tool];
|
||||
let onMove = tool(pos, this.state, dispatch);
|
||||
if (onMove) return pos => onMove(pos, this.state);
|
||||
});
|
||||
this.controls = controls.map(
|
||||
Control => new Control(state, config));
|
||||
this.dom = elt("div", {}, this.canvas.dom, elt("br"),
|
||||
...this.controls.reduce(
|
||||
(a, c) => a.concat(" ", c.dom), []));
|
||||
}
|
||||
setState(state) {
|
||||
this.state = state;
|
||||
this.canvas.setState(state.picture);
|
||||
for (let ctrl of this.controls) ctrl.setState(state);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
指定给`PictureCanvas`的指针处理器,使用适当的参数调用当前选定的工具,如果返回了移动处理器,使其也接收状态。
|
||||
|
||||
所有控件在`this.controls`中构造并存储,以便在应用状态更改时更新它们。 `reduce`的调用会在控件的 DOM 元素之间引入空格。 这样他们看起来并不那么密集。
|
||||
|
||||
第一个控件是工具选择菜单。 它创建`<select>`元素,每个工具带有一个选项,并设置`"change"`事件处理器,用于在用户选择不同的工具时更新应用状态。
|
||||
|
||||
```js
|
||||
class ToolSelect {
|
||||
constructor(state, {tools, dispatch}) {
|
||||
this.select = elt("select", {
|
||||
onchange: () => dispatch({tool: this.select.value})
|
||||
}, ...Object.keys(tools).map(name => elt("option", {
|
||||
selected: name == state.tool
|
||||
}, name)));
|
||||
this.dom = elt("label", null, "🖌 Tool: ", this.select);
|
||||
}
|
||||
setState(state) { this.select.value = state.tool; }
|
||||
}
|
||||
```
|
||||
|
||||
通过将标签文本和字段包装在`<label>`元素中,我们告诉浏览器该标签属于该字段,例如,您可以点击标签来聚焦该字段。
|
||||
|
||||
我们还需要能够改变颜色 - 所以让我们添加一个控件。 `type`属性为颜色的 HTML `<input>`元素为我们提供了专门用于选择颜色的表单字段。 这种字段的值始终是`"#RRGGBB"`格式(红色,绿色和蓝色分量,每种颜色两位数字)的 CSS 颜色代码。 当用户与它交互时,浏览器将显示一个颜色选择器界面。
|
||||
|
||||
该控件创建这样一个字段,并将其连接起来,与应用程序状态的`color`属性保持同步。
|
||||
|
||||
```js
|
||||
class ColorSelect {
|
||||
constructor(state, {dispatch}) {
|
||||
this.input = elt("input", {
|
||||
type: "color",
|
||||
value: state.color,
|
||||
onchange: () => dispatch({color: this.input.value})
|
||||
});
|
||||
this.dom = elt("label", null, "🎨 Color: ", this.input);
|
||||
}
|
||||
setState(state) { this.input.value = state.color; }
|
||||
}
|
||||
```
|
||||
|
||||
## 绘图工具
|
||||
|
||||
在我们绘制任何东西之前,我们需要实现一些工具,来控制画布上的鼠标或触摸事件的功能。
|
||||
|
||||
最基本的工具是绘图工具,它可以将你点击或轻触的任何像素,更改为当前选定的颜色。 它分派一个动作,将图片更新为一个版本,其中所指的像素赋为当前选定的颜色。
|
||||
|
||||
```js
|
||||
function draw(pos, state, dispatch) {
|
||||
function drawPixel({x, y}, state) {
|
||||
let drawn = {x, y, color: state.color};
|
||||
dispatch({picture: state.picture.draw([drawn])});
|
||||
}
|
||||
drawPixel(pos, state);
|
||||
return drawPixel;
|
||||
}
|
||||
```
|
||||
|
||||
该函数立即调用`drawPixel`函数,但也会返回它,以便在用户在图片上拖动或滑动时,再次为新的所触摸的像素调用。
|
||||
|
||||
为了绘制较大的形状,可以快速创建矩形。 矩形工具在开始拖动的点和拖动到的点之间画一个矩形。
|
||||
|
||||
```js
|
||||
function rectangle(start, state, dispatch) {
|
||||
function drawRectangle(pos) {
|
||||
let xStart = Math.min(start.x, pos.x);
|
||||
let yStart = Math.min(start.y, pos.y);
|
||||
let xEnd = Math.max(start.x, pos.x);
|
||||
let yEnd = Math.max(start.y, pos.y);
|
||||
let drawn = [];
|
||||
for (let y = yStart; y <= yEnd; y++) {
|
||||
for (let x = xStart; x <= xEnd; x++) {
|
||||
drawn.push({x, y, color: state.color});
|
||||
}
|
||||
}
|
||||
dispatch({picture: state.picture.draw(drawn)});
|
||||
}
|
||||
drawRectangle(start);
|
||||
return drawRectangle;
|
||||
}
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user