我如何仅使用HTML5和SVG构建Paint 4 Kids Windows应用商店





5.00/5 (5投票s)
本文将讨论在 Paint 4 Kids 中使用可缩放矢量图形 (SVG),从项目的一些需求出发,然后解决这些需求以及使用 SVG 时可能遇到的一些陷阱。
Paint 4 Kids 是一款 Windows 应用商店应用,专为儿童设计。一个简单的涂色和绘画应用。您可以在 Windows 应用商店网站上直接阅读其消费者功能,也可以在那里看到一些截图。对于本文,您可以简单地将其想象成一个包含多个图画并可与之交互的应用。
从技术角度来看,它完全使用标准的 Web 技术构建,如 HTML、CSS、JavaScript 和 SVG。请考虑一个重要方面:使用 Windows 8,我们正在重用我们的 Web 技能来编写应用,并在 Web 平台上进行开发,利用 Internet Explorer 10 渲染引擎底层对 HTML5 的支持。因此,一个直接的好处是我们不必测试和支持所有不同的浏览器版本,也不必使用某些 polyfills 来模拟旧浏览器中的新 API 功能。我们可以利用 Web 平台的优势,并在 JavaScript 中直接使用 Windows 8 的特定 API。
本文将讨论在 Paint 4 Kids 中使用可缩放矢量图形 (SVG),从项目的一些需求出发,然后解决这些需求以及使用 SVG 时可能遇到的一些陷阱。我希望其中一些考虑因素也能适用于您的应用。
需求和使用 SVG 的原因
Windows 应用商店应用在您可以分发和销售应用的市场以及您的应用可以运行的设备方面,为您提供了绝佳的机会。设想您的应用可以在具有**不同屏幕尺寸、不同屏幕分辨率和像素密度**的设备上使用。这一点必须加以考虑,阅读本文将帮助您深入了解如何将您的应用缩放到不同的屏幕,以及如何使用 Windows 模拟器测试您的应用。
其中一个需求是,我们希望**一个单一的图画在不同分辨率下都能显示良好**。我们最终使用了 SVG,这是一种用于二维图形的可缩放矢量图像格式。我们的方法是创建一个特定分辨率为 2560x1440 的图画(稍后会详细介绍),然后将其缩放到用户当前的分辨率。另一个优点是,在 SVG 中**用颜色填充路径非常简单快捷**,这是应用的主要功能之一。当用户点击屏幕时,很容易截获图画的相应部分并用特定颜色填充路径。
另一个需求是,我们**希望重用一些在 XAML 中制作的图画**,并获得良好的工具支持,以便我们能够简单快捷地创建和添加新图画到应用中。Inkscape 非常适合此目的。您可以导入 XAML 图画并将其导出为 SVG 文件,由于这两种基于矢量的格式非常相似,因此导出几乎总是成功。
使用 SVG 的一个缺点是,随着添加到 DOM 的对象数量的增加,DOM 操作可能会变慢,因此性能测试和优化通常是必要的。您可以阅读这篇 Internet Explorer 博客文章,其中包含关于 SVG 与 Canvas 的优缺点的详细表格,以帮助您决定何时使用 Canvas、SVG 或两者的组合。Paint 4 Kids 的另一个需求是**用户能够保存他们的图画**。不幸的是,在撰写本文时,无法从 SVG 文件创建图像,因此我们最终将 SVG 文件转换为 Canvas 对象。
另一种方法是使用 Canvas 而不是 SVG。使用 Canvas,您必须为不同的分辨率创建不同的原始图像,至少如果您希望您的图画在不同分辨率下看起来非常好,否则线条会显得像素化。另一个考虑是如何填充图画中的形状。在使用 Canvas 和触摸点时,您处理的是由像素组成的原始图像;您无法轻松地处理矩形、圆形和路径等形状。如果您想填充一个被黑色线条包围的形状,您需要实现一个洪水填充(或种子填充)算法来实现此目的,如果待填充的区域很大(相对于屏幕尺寸),您将看到该区域正在填充,而使用 SVG 时,填充效果几乎是瞬时的。
适应不同外形尺寸的图画:视口 (viewport) 和 viewBox
如前所述,我们从一个特定分辨率为 2560x1440 像素的 SVG 图画开始,每个图画都是从 Windows 应用商店应用内的文件中异步加载到 DOM 中。选择此特定值是为了方便,您也可以设置其他值。在 Paint 4 Kids 中,我们希望使用一个单一的图画并使其适应不同的分辨率。看看下面在 Windows 模拟器中运行的不同分辨率下的相同图画的图像。
第一张图片模拟了 1024x768 像素分辨率的 10 英寸显示器,第二张图片模拟了 2560x1440 分辨率的 27 英寸显示器。
这种固定分辨率为我们提供了一种虚拟空间坐标系。在 SVG 中,我们可以使用 `viewBox` 属性来设置这个坐标系。如果您使用工具在此坐标系中绘图,所有图形元素都相对于此坐标系。现在,如果您想从这个分辨率缩放到特定分辨率,例如,假设您的平板电脑分辨率为 1366x768,您只需设置包含您图画的 SVG 元素的 `width` 和 `height` 属性。最后两个属性定义了 SVG 的 `viewport`,在这个场景下也就是我们设备的实际分辨率。
`viewBox` 的值包含四个数字,代表您想映射和缩放到视口的坐标系的最小 *x* 坐标、最小 *y* 坐标、宽度和高度。因此,结合 `viewBox`、`width` 和 `height` 属性,我们可以得到预期的结果。
以下是每个图画的根 SVG 元素,一个简单的 XML 文件。坐标系从屏幕的左上角开始,正如您可能已经处理过 SVG 时所预期的那样。
<svg viewbox="0 0 2560 1440">...
当图画加载到 DOM 中时,这发生在用户选择要上色的图画时,我们可以设置 `viewport` 属性。
为了找到一个未命名 SVG 元素的根,我们使用 `document.querySelector` API,使用伪类选择器来查找第一个 SVG 元素。由于此 API 只调用一次,即当用户选择一个要绘制的元素时,我们可以忽略任何性能延迟。
var svgd; svgd = document.querySelector("svg:first-of-type"); svgd.setAttribute("width", window.innerWidth); svgd.setAttribute("height", window.innerHeight);
代码示例还使用 window 对象及其 `inner*` 属性在运行时获取实际像素分辨率。
处理 `viewport` 和 `viewBox` 时另一个需要考虑的是**纵横比**。如果两个坐标系的宽度和高度比例不同,有时您希望结果图像能非均匀地适应。在其他情况下,您可能希望保留纵横比并均匀缩放图像。SVG 使用 `preserveAspectRatio` 属性来决定图像是否需要均匀缩放。稍后在讨论“印章”时我们将对此进行讨论。对于图画,将 `viewBox` 均匀缩放到适应视口的默认行为正是我们想要的。
如何用颜色和图像填充路径
填充 SVG 形状,如路径、矩形或其他形状,是一个非常简单的步骤,并且几乎是瞬时的,因为这就像在 CSS 中设置一个**样式属性**一样,所以您不需要编写代码来找到被线条包围的每个像素。您可以查看下面的代码,这是在 SVG 区域内触发点击事件时的回调函数。
var el = document.elementFromPoint(e.x, e.y); var selectedColor = "255,0,0,1"; el.setAttribute("style", "fill:rgba(" + selectedColor + "); stroke-width:3;);
在上面的代码中,`e` 是 `MSPointerEvent` 类型的对象。这个对象非常重要,如果您订阅了某些 `MSPointer*` 事件(如 `MSPointerDown`),就可以获得这个对象。只需一行代码,您就可以订阅一个来自鼠标、触摸甚至笔的事件!此外,在 `MSPointerEvent` 中,如果需要,您可以读取 `pointerType` 属性,它提供了生成事件的设备类型的详细信息。如果您对此主题感兴趣,可以阅读这篇关于 IE 10 和 Windows 应用商店应用中触摸输入 API 的博文。
回到代码,这里的 `e` 对象仅用于使用 `elementFromPoint` API 获取输入设备的 x、y 点坐标。现在 `el` 是我们要用颜色填充的特定形状,它的类型是 `SVGPathElement`。其余代码很简单,设置 `SVGPathElement` 的填充颜色和描边宽度,而不管它实际是什么形状。
除了使用 `setAttribute` API,您还可以直接在 `SVGPathElement` 上设置 `fill` 和 `strokeWidth` 属性,避免字符串解析可以提高性能;尽管在此场景下用户可能察觉不到。
在示例中,颜色是用户可以从调色板中选择的标准纯 RGBA 颜色,但您也可以用来自图像的路径或使用渐变来填充形状。Paint 4 Kids 不仅定义了一组常用颜色,还定义了一些图像,如石头、草等。在 SVG 中执行此操作,您可以定义一些**图案**,如下面的代码所示。
<pattern id="imgPatterns_1" patternUnits="userSpaceOnUse" width="128" height="128"> <image xlink:href="../images/BRICK.PNG" width="128" height="128" /> </pattern>
代码中有两点需要注意:第一,我们定义了一个 `SVGPatternElement`,它基于所包含的图像。第二,您可以定义 `patternUnits` 属性,该属性定义了如何用图案本身填充形状。`userSpaceOnUse` 简单地重复图像,次数越多,它们之间没有填充。一旦定义了图案,我们就可以使用下面的语法在上述 `fill` 属性中使用它。
var selectedColor = "url(#imgPatterns_1)"; el.setAttribute("style", "fill:" + selectedColor + "; stroke-width:3;);
查看代码,您会注意到 `fill` 属性现在是一个 URL,它使用上面定义的图案元素的 ID,前面加上 `#` 符号(例如 ` #imgPattern_1`)。
请参阅下面的图像,了解一些图案效果的实际应用。
为“印章”重用 SVG 元素
Paint 4 Kids 还为您提供了将一些形状插入到您的图画中作为印章的可能性,您可以将它们附加到绘画上。对用户而言,印章是一个像南瓜或球这样的形状,他们可以将其放置在图画中的任何位置,他们也可以决定形状的大小;他们可以反复将相同的形状插入到图画中。
从 SVG 的角度来看,每个形状都可以根据您的需要变得复杂,因此反复插入相同的形状可能会使您的 DOM 越来越大,最终在慢速设备上导致您的应用出现性能延迟。在此优化管理此场景的方式至关重要。
为了实现此功能,我们结合使用了 symbol 和 use 元素。symbol 元素提供了一种**组合元素**的方式,但当 symbol 元素添加到页面 DOM 时,它不会显示出来,因此您可以将其用作可以**重用**的东西。因此,我们可以将一个复杂的 SVG 形状插入一次,并使用 use 关键字反复重用它。在下面的示例中,您可以看到一些 SVG 代码。
<symbol id="bottiglia" viewBox="0 0 40 40" preserveAspectRatio="xMinYMin meet" > <path …> </symbol> <use id="onlyforimg" xlink:href="#bottiglia" height ="80" width="80"></use>
包含实际形状的路径(为简洁起见已省略)包含在一个 symbol 元素中,该元素还定义了一个 `viewBox` 来设置形状原始绘制时的大小。此处 `viewBox` 是必需的,因为我们可以调整 use 元素下方的形状大小,我们在其中设置了高度和宽度,将原始尺寸加倍——这是另一个**期望**,如上所述。use 元素引用了 symbol 元素的 `id` 属性,正如您从上面的代码中容易注意到的那样。此外,use 非常小,正因如此,我们可以反复重用它,最终改变图画的大小,减小 DOM 的大小。
在实际应用中,use 元素是通过 JavaScript 动态创建的,不仅设置了宽度和高度,还设置了 x 和 y 坐标来定位形状,使其中心位于用户点击屏幕的位置。为了做到这一点,需要一些数学计算(此处未解释)来设置这两个坐标。为了使所有这些工作正常进行,我们需要使用 `preserveAspectRatio` 并使用 `meet` 属性来获得均匀缩放,以便 `viewBox` 被完全包含,而不是在映射到视口时被裁剪,同时还要设置**对齐**。`xMin` 将 `viewBox` 的最小 `x` 与视口的左角对齐,`yMin` 将 `viewBox` 的最小 `y` 与视口的顶部边缘对齐。您可以在此处从 SVG 规范中获取这些值的完整参考。
加载 SVG 文件
本文的前一部分讨论了可能直接在现代浏览器中运行的标准 SVG。从现在开始,我们将混合一些可用于构建 Windows 应用商店应用的 Windows 应用商店 API,这些 API 特定于 Windows 8 平台,并且最终可以从其他支持的语言调用。
由于 Paint 4 Kids 使用了许多图画,一个图画会在需要时从 `appx` 文件异步加载,然后放入页面的 DOM 中。异步加载是使用 Windows 8 的异步加载 API 完成的,如代码所示。Windows 应用商店应用禁止我们将动态代码加载到 DOM 中,这是因为通常这些代码可能来自外部调用,并且如果代码是恶意的,最终会在您的应用中产生一些安全问题。但是,我们加载的代码是受信任的,因为它已包含在 `appx` 文件中,并且由我们提供。您可以安全地调用 `WinJS.Utilities.setInnerHTMLUnsafe` 函数,该函数允许我们在需要时将图画加载到 DOM 中。
svgfolder.getFileAsync("drawing.svg").then(function (file) {
file.openAsync(Windows.Storage.FileAccessMode.read).then(function (stream) {
inputStream = stream.getInputStreamAt(0);
reader = new Windows.Storage.Streams.DataReader(inputStream);
size = stream.size;
reader.loadAsync(size).then(function () {
svg = reader.readString(size);
WinJS.Utilities.setInnerHTMLUnsafe(document.getElementById("s1"), svg);
});
});
});
`svgFolder` 是 `StorageFolder` 类型的对象,您可以调用一个 `async` 方法来搜索一个 `storageFile` 对象(此处省略了错误处理),然后打开一个流和一个 `dataReader` 对象来读取内容。最后,`svg` 变量包含一个表示整个 SVG 图画的字符串。如上所述,我们可以调用 `setInnerHTMLUnsafe` 将图画添加到 `s1` DOM 元素中。
这里的好处是,SVG 可以在 Visual Studio 2012 的 DOM 资源管理器中进行检查,如下图所示。
您可以在运行时选择应用中的特定形状(这也适用于模拟器),DOM 资源管理器将向您显示相应的 SVG,这可能是动态加载和后续转换的结果。非常酷!
将 SVG 图画转换为 Canvas 并保存为图像
有时您需要图画的 jpeg 图像,例如,如果您想将图画保存到文件系统,或者如果您想使用 Windows 8 的共享 charm,或者在应用的其他部分中使用。为此,我们使用了 canvg 库,**该库可以将 SVG 文件转换为 Canvas 对象**。转换可能需要一些时间,具体取决于 SVG 文件的大小和您的硬件平台,因此您可以通知用户正在进行某项操作,我们使用**浮出控件**(flyout)和一个进度环,在转换开始时显示。
现在您已经获得了 Canvas 对象,您可以获取整个字节数组的引用,然后可以使用 Windows 编码 API 来获取所需格式和大小的图像。下面是一个示例函数。
function doSaveDrawingToFileEnd (fil) {
var Imaging = Windows.Graphics.Imaging;
var stream, encoderId, cc, offset, outputPixelData;
fil.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (_stream){
stream = _stream;
return Imaging.BitmapEncoder.createAsync(jpegEncoderId, stream);
}).then(function (encoder) {
cc = document.getElementById('canvas');
//ignore the realBRectHeight variable below
//it is not important to understanding the logic
offset = (height - realBRectHeight) / 2;
outputPixelData = cc.getContext("2d").getImageData(0, offset, cc.width,cc.height - offset);
encoder.setPixelData(Imaging.BitmapPixelFormat.rgba8, Imaging.BitmapAlphaMode.straight, width, realBRectHeight, 90, 90, outputPixelData.data);
return encoder.flushAsync();
}).then(null, function (error) {
console.log(error.message);
}).then(function () {
if (stream) stream.close();
})
}
首先,您获取一个 `storageFile` 对象的引用(代码中未显示),`fil` 变量将是我们的最终 jpeg 图像;然后您打开一个用于写入的流并创建一个 `BitmapEncoder` 对象,将编码类型设置为 `jpegEncoderId`,这用于将字节数组编码为您所需的图像。`cc` 变量是对包含已转换 SVG 文件的 Canvas 对象的引用。使用标准的 `getImageData` 方法,我们获取要转换的字节(在代码中您可以看到一个偏移值用于裁剪图画的特定部分,但在这里您可以忽略此值,因为它对于理解逻辑来说并不特别有用)。现在,使用编码器的 `setPixelData` API,您可以为要生成的图像设置一些值,图像实际上是在调用 `flushAsync` 时生成的。
总结
在本文中,我们看到了关于 SVG 的一些技术考虑以及它如何在 Paint 4 Kids 中使用。SVG 是一项非常有趣且强大的 Web 技术,您可以利用它使用 JavaScript 构建出色的 Windows 应用商店应用。您也可以直接在浏览器中**玩转** SVG,在 IE Test Drive 网站上搜索一些**有趣的**示例。如果您遇到类似的情况,希望这篇文章能有所帮助。
如果您想了解更多关于如何构建 Windows 应用商店应用的信息,请查看 Generation App。
本文由 Pietro Brambati 撰写。Pietro 是一位充满激情的开发者忍者。他喜欢使用各种语言和框架,处理不同规模的应用,从移动设备应用到大型企业级应用。他曾作为微软的技术布道师,有机会与开发者和学术界合作。您可以在意大利各地的主要开发者活动和黑客马拉松上找到他,也可以通过他的博客或 Twitter 联系他。