还记得上次遇到UI错误并花费数小时才能修复的问题吗?可能没有明显原因,此错误是定期发生的。也许它是在某些条件下出现的(可能取决于设备,操作系统,浏览器或用户的操作),或者它隐藏在Web项目客户端的众多前端技术之一的深处?
最近,我不得不记住UI错误的原因是多么令人困惑。也就是说,我们正在谈论修复一个有趣的错误,该错误会影响Safari浏览器中SVG图像的输出。发生此错误时没有任何特定的系统,并且没有明显的原因。遇到问题时,我试图找到类似的案例,希望对此类案例的描述能给我一些提示。但是我一直找不到任何有用的东西。没错,尽管我面前遇到了种种障碍,但我还是能够应付这个错误。
我已经使用本文将要介绍的一些调试策略对问题进行了分析。摆脱错误后,我想起了建议几年前,克里斯·科耶(Chris Coyer)给他的Twitter读者。这个建议是这样的:“写出您在访问搜索引擎时想要找到的文章。” 实际上,这就是我所做的。
问题概述
在我正在进行的一个项目中,我在一个实时站点上发现了一个错误,该错误的表现我记录在此视频中。这是按钮在其正常状态下的外观。
处于正常状态
的按钮这是出现问题后的相同按钮。
按钮的一部分被切断了,
我在各种情况下都重现了此错误,从而导致页面重新绘制。例如,它发生在调整浏览器窗口大小时。
为了演示该问题,我在CodePen上创建了一个示例。我们将在下面更详细地讨论它。但是您可以自己尝试该示例。即,我们正在谈论的事实是,如果您在Safari浏览器中打开此示例,那么在页面加载时,按钮将看起来像预期的那样。但是,如果单击两个较大的按钮之一,则该错误将突出其丑陋的头部。
为什么会裁剪SVG图像?
每次事件发生时
paint
,较大按钮中使用的SVG图像均无法正确显示。这些图像仅被裁剪。毫无明显原因,页面加载可能会发生这种情况。即使调整窗口大小也可能发生这种情况。通常,错误会在多种情况下出现。
发生错误的项目的概述
我认为在谈论错误时,最好公开有关项目及其发生条件的详细信息。
- 该项目使用React(但本文的读者不需要了解React就可以理解它)。
- SVG图像作为React组件导入到项目中,并使用webpack嵌入HTML中。
- . .
- CSS.
- , , HTML-
<button>
. - Safari ( 13 ).
让我们看一下错误并考虑是否可以对发生的事情做出任何假设。此类错误的原因通常不在表面上的某个地方,因此,面对此类错误,无法立即确定发生了什么。在我们首次尝试理解问题时,我们不必努力以100%的准确性确定其原因。我们将逐步调查错误,提出并检验假设,这将有助于我们缩小发生情况的可能原因的范围。
提出假设
乍一看,发生的事情看起来像CSS错误。也许,当您将鼠标悬停在按钮上时,会应用一些样式,这会破坏布局。也许
overflow
SVG图像属性值得指责。此外,由于各种原因(paint
重新调整浏览器窗口的大小,鼠标指针在按钮上,单击时等等),在重新绘制页面时,还可能会在没有任何特定系统的情况下发生错误。
让我们从最简单,最明显的假设开始。假设错误出在CSS中。我们可以假设Safari浏览器中存在一个错误,当将特定样式应用于SVG元素时,会导致SVG输出不正确。例如,类似于用于构建弹性布局的样式。
我们刚刚提出了一个假设。我们的下一步是进行检验,以证实或否定这一假设。每次测试的结果将为我们提供有关错误的新信息,并有助于制定以下假设。
简化问题
我们将使用一种称为“简化问题”的调试策略。这将使我们能够查明错误的确切位置。在康奈尔大学的一次计算机科学讲座中,这种策略被描述为“一种逐渐摆脱与错误无关的代码的方法”。
假设错误出在CSS上,我们最终可以找到错误的原因,也可以从等式中排除CSS,这将减少可能的错误原因并降低问题的复杂性。
让我们检验一下我们的假设。让我们尝试确认一下。在这种情况下,如果您暂时禁用页面中的所有非标准样式,这将导致错误不再出现。
以下是包含适当样式表的代码:
import 'css/app.css';
我创建了这个CodePen项目来演示没有CSS的元素的输出。在React中,SVG图形作为组件导入到项目中,然后使用webpack将相应的代码嵌入HTML中。
常规按钮视图
如果您在Safari中打开上述项目,然后单击其中一个大按钮,则表明错误仍然存在。页面加载时也会发生这种情况,但是在使用CodePen时,您需要单击按钮以触发错误。
即使禁用了CSS,该错误仍然存在(Safari 13)
。因此,我们可以得出结论,CSS与它无关。但是,我们要注意一个事实,在这种情况下,五个按钮中只有两个显示不正确。让我们记住这一点,然后继续下一个假设。
错误隔离
我们的下一个假设是Safari在HTML元素内呈现SVG图像时存在错误
<button>
。由于在显示前两个按钮时会出现问题,因此我们隔离第一个按钮,然后看看会发生什么。
莎拉·德拉兹纳(Sarah Drazner)在此该材料说明了绝缘的重要性。我强烈建议任何对调试工具和查找错误的不同方法感兴趣的人员阅读本材料。这是该材料的引文:“隔离也许是调试的最重要的基本原理。我们项目中使用的代码可以分散在不同的库和框架中。许多人可能会参与项目的工作,其中一些为项目的开发做出了贡献的人不再从事这些工作。隔离问题有助于我们缓慢切断不导致错误出现的原因。最后,这可以找到问题的根源并专注于此。”
故障隔离通常被称为“速记测试案例”。
我将按钮移到了单独的空白页(为此创建了单独的测试)。即,创建此CodePen项目是为了单独研究按钮。虽然我们已经得出结论,CSS并不是问题的根源,但是我们需要禁用样式,直到发现问题的真正原因为止。这将使我们尽可能简化问题。
仅带有按钮的页面
如果您在Safari中打开该项目,事实证明我们不再会导致错误。即使单击该按钮,图像也不会改变。但是,这不能被认为是解决问题的可接受方案。但是,此CodePen项目的代码为我们提供了一个创建最少的可复制示例的良好基础。
最小的可复制示例
以前的两个CodePen项目之间的主要区别是按钮组合。通过检查所有可能的按钮组合,我们可以得出结论,仅当
paint
较大的SVG图像发生事件时才出现此问题,然后在页面上放置较小的SVG图像。
知道这一点后,我们就可以创建一个最小的可重现示例,从而使我们可以重现错误并仍然消除任何不必要的元素。借助最少的可复制示例,我们可以更深入地研究问题,并突出显示导致问题的代码部分。
这是有问题的CodePen项目。
最小的可复制示例
如果您在Safari中打开该项目并单击按钮,我们再次遇到问题。这将使我们能够提出两个SVG元素以某种方式相互冲突的假设。如果将第二个SVG图像叠加在第一个SVG图像上,则会发现第一个图像背景的剩余尺寸与较小图像的尺寸完全匹配。
由于错误而使第二张图像放置在第一张图像上方的图形
分而治之
我们能够使用一对SVG图像表示的最少元素来重现该错误。现在,我们将进一步缩小问题的范围,将范围缩小到问题的具体源SVG代码。如果我们大致理解了SVG代码,但仍然希望找到问题的根源,那么我们可以使用采用分而治之方法的二叉树搜索策略。这是演讲的另一节摘录来自康奈尔大学计算机科学学院的教授说:“例如,您可以先检查一大段代码,然后将验证代码放在要检查的代码中间。如果此处未发生错误,则表示其来源位于代码的后半部分。否则,其源位于代码的前半部分。”
在检查SVG代码时,您可以尝试
<filter>
从第一张图片的描述中删除一个元素(并且<defs>
,因为该块中始终没有内容)。让我们关注一下元素可以解决的任务<filter>
。这方面的一个很好的解释可以发现在这里。即,我们正在谈论以下内容:“要将滤镜应用于SVG图像,有一个特殊元素称为<filter>
...它本质上类似于设计用于线性渐变,蒙版,模板和其他图形效果的元素。该元素<filter>
永远不会单独显示。它仅用作可以通过filter
SVG代码中的属性或url()
CSS中的函数进行引用的东西。”
在我们的SVG图像中,使用滤镜在图像底部添加一些内部阴影。从第一张图片的代码中删除过滤器后,我们等待该阴影消失。如果之后问题仍然存在,那么我们可以得出结论,描述SVG元素的其他代码有问题,
我创建了另一个CodePen项目,以演示该测试的结果。
删除<filter>元素
的后果正如您可以轻易看到的那样,该问题并未解决。并且即使删除了滤镜代码,内部阴影仍会继续显示。但是现在,该问题出现在所有浏览器中。这使我们可以得出结论,该错误在其余按钮描述代码中。如果
id
从中删除其余部分<g filter="url(#filter0_ii)">
,则阴影消失。这里发生了什么?
让我们再来看一下元素的上述定义,
<filter>
并注意以下词语:“元素<filter>
永远不会单独显示。它仅用作可以使用filter
SVG中的属性引用的内容。” (我突出显示了文本的一部分。)
因此,知道了这一点,我们可以得出结论,将第二个SVG图像的过滤器声明应用于第一个SVG图像,这会导致错误。
错误修复
现在我们知道我们的问题与元素有关
<filter>
。我们也知道两个SVG都有这个元素,因为滤镜用于创建圆形内部阴影。让我们比较两个SVG图像的代码,并考虑是否可以解释该错误并进行修复。
我简化了两个图像的代码,以便您可以清楚地看到其中发生了什么。
这是第一个SVG图片的代码:
<svg width="46" height="46" viewBox="0 0 46 46">
<g filter="url(#filter0_ii)">
<!-- ... -->
</g>
<!-- ... -->
<defs>
<filter id="filter0_ii" x="0" y="0" width="46" height="46">
<!-- ... -->
</filter>
</defs>
</svg>
这是第二张图片的代码:
<svg width="28" height="28" viewBox="0 0 28 28">
<g filter="url(#filter0_ii)">
<!-- ... -->
</g>
<!-- ... -->
<defs>
<filter id="filter0_ii" x="0" y="0" width="28" height="28">
<!-- ... -->
</filter>
</defs>
</svg>
分析这两个片段,您会注意到在构造中
id=filter0_ii
使用了相同的标识符。 Safari将最后一个浏览器解析的过滤器定义应用于元素(在本例中,第二个图像的过滤器)。这导致第一张图像被裁剪的事实。其原始大小为48px
,并在应用过滤器后将其切出一块大小26px
。id
DOM属性必须具有唯一值。如果页面上有多个相同的页面id
,浏览器将无法确定需要使用哪个页面。而且由于filter
每个事件发生时都会覆盖该属性paint
,然后,取决于首先准备好哪个定义(此处会出现竞争情况),该错误是否出现。
让我们尝试
id
在每个图像的代码中分配唯一值,然后查看结果。这是相应的CodePen项目。
分配唯一的ID解决了该问题
如果现在在Safari中打开项目并单击按钮,则可以通过分配
id
SVG图像中使用的唯一过滤器来确保我们解决了该问题。如果您考虑到该项目的属性具有非唯一值的事实,id
则会得出结论,该问题应该已经出现在所有浏览器中,而不仅仅是Safari。但是由于某种原因,其他浏览器(包括Chrome和Firefox)似乎已经正确处理了这种异常情况。不过,这可能只是一个巧合。
结果
这是又一次冒险!我们开始只知道项目中存在某种错误,有时会出现这种错误,然后再没有这种错误,但是最后我们完全了解了发生这种情况的原因并解决了该问题。如果您不了解发生了什么,调试用户界面代码并弄清楚图形失真的原因可能很棘手。对我们来说幸运的是,有一些调试策略可以帮助我们找到即使是最令人困惑的错误的根本原因。
首先,我们通过提出一些假设来简化问题,这些假设使我们能够从项目中删除与错误无关的组件(样式,标记,动态事件等)。之后,我们隔离了标记并找到了一个最小的可复制示例。这使我们可以专注于一小段代码。最后,我们使用分而治之的策略消除了错误,从而确定了问题所在。
感谢所有花时间阅读本文的人。但是,在结束对话之前,让我告诉您有关讲座中提到的另一种调试策略。康奈尔大学。关键是在工作过程中,您需要休息,休息并使头脑从所有想法中解放出来:“如果调试花费太多时间,那么程序员会感到疲倦。事实证明,他处于这种状态是徒劳的。在这种情况下,值得休息一下,把所有东西扔掉。过了一会儿,您应该尝试从不同的角度看问题。”
您如何解决无法理解的错误?