- 练习
- 键盘绑定
- 高效绘图
- 圆
- 合适的直线
练习
我们的程序还有提升空间。让我们添加一些更多特性作为练习。
键盘绑定
将键盘快捷键添加到应用。 工具名称的第一个字母用于选择工具,而control-Z或command-Z激活撤消工作。
通过修改PixelEditor组件来实现它。 为<div>元素包装添加tabIndex属性 0,以便它可以接收键盘焦点。 请注意,与tabindex属性对应的属性称为tabIndex,I大写,我们的elt函数需要属性名称。 直接在该元素上注册键盘事件处理器。 这意味着你必须先单击,触摸或按下 TAB 选择应用,然后才能使用键盘与其交互。
请记住,键盘事件具有ctrlKey和metaKey(用于 Mac 上的Command键)属性,你可以使用它们查看这些键是否被按下。
<div></div><script>// The original PixelEditor class. Extend the constructor.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, dispatch);}});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);}}document.querySelector("div").appendChild(startPixelEditor({}));</script>
高效绘图
绘图过程中,我们的应用所做的大部分工作都发生在drawPicture中。 创建一个新状态并更新 DOM 的其余部分的开销并不是很大,但重新绘制画布上的所有像素是相当大的工作量。
找到一种方法,通过重新绘制实际更改的像素,使PictureCanvas的setState方法更快。
请记住,drawPicture也由保存按钮使用,所以如果你更改它,请确保更改不会破坏旧用途,或者使用不同名称创建新版本。
另请注意,通过设置其width或height属性来更改<canvas>元素的大小,将清除它,使其再次完全透明。
<div></div><script>// Change this methodPictureCanvas.prototype.setState = function(picture) {if (this.picture == picture) return;this.picture = picture;drawPicture(this.picture, this.dom, scale);};// You may want to use or change this as wellfunction drawPicture(picture, canvas, scale) {canvas.width = picture.width * scale;canvas.height = picture.height * scale;let cx = canvas.getContext("2d");for (let y = 0; y < picture.height; y++) {for (let x = 0; x < picture.width; x++) {cx.fillStyle = picture.pixel(x, y);cx.fillRect(x * scale, y * scale, scale, scale);}}}document.querySelector("div").appendChild(startPixelEditor({}));</script>
圆
定义一个名为circle的工具,当你拖动时绘制一个实心圆。 圆的中心位于拖动或触摸手势开始的位置,其半径由拖动的距离决定。
<div></div><script>function circle(pos, state, dispatch) {// Your code here}let dom = startPixelEditor({tools: Object.assign({}, baseTools, {circle})});document.querySelector("div").appendChild(dom);</script>
合适的直线
这是比前两个更高级的练习,它将要求你设计一个有意义的问题的解决方案。 在开始这个练习之前,确保你有充足的时间和耐心,并且不要因最初的失败而感到气馁。
在大多数浏览器上,当你选择绘图工具并快速在图片上拖动时,你不会得到一条闭合直线。 相反,由于"mousemove"或"touchmove"事件没有快到足以命中每个像素,因此你会得到一些点,在它们之间有空隙。
改进绘制工具,使其绘制完整的直线。 这意味着你必须使移动处理器记住前一个位置,并将其连接到当前位置。
为此,由于像素可以是任意距离,所以你必须编写一个通用的直线绘制函数。
两个像素之间的直线是连接像素的链条,从起点到终点尽可能直。对角线相邻的像素也算作连接。 所以斜线应该看起来像左边的图片,而不是右边的图片。
如果我们有了代码,它在两个任意点间绘制一条直线,我们不妨继续,并使用它来定义line工具,它在拖动的起点和终点之间绘制一条直线。
<div></div><script>// The old draw tool. Rewrite this.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;}function line(pos, state, dispatch) {// Your code here}let dom = startPixelEditor({tools: {draw, line, fill, rectangle, pick}});document.querySelector("div").appendChild(dom);</script>
