起
现象
最初是我发现《基于Java的后端开发入门:2.面向对象》 这篇文章通过iPhone设备无法访问。
其状况如下:
而且不仅仅是我的iPhone,我找很多人帮我试了,都无法访问。
是JS吗
最初我判断是有某个异常的JS脚本导致的。
但是其他的页面都是OK的,唯独这个页面打不开,我也没有为这个页面专门写过JS。
这个页面相比其他大多数的页面有一个特点,我通过html的iframe标签引入了哔哩哔哩。但是我这个博客通过iframe标签引入哔哩哔哩的页面不止这一个,其他都是正常的。
但无论如何,首先判断是不是JS。
我采用了一个最直接的方法,直接禁用浏览器的JS。
结果非常的出乎我的意料,还是打不开。
是CSS吗
然后我开始通过谷歌搜索,终于让我搜索到了一点内容,是有一个CSS,iOS的Safari浏览器解析不了。但是我发现,那个解析不了的CSS,我似乎并没有用。
但无论如何,不是JS,就只可能是CSS了。
我怀疑是我的文章中有某段内容导致的,这段内容在经过Hexo的渲染之后,生成了iOS的Safari解析不了的CSS。
为了定位到那段内容,我先只用一小块进行渲染,测试一下,是OK的,然后我再加一小块,在也是OK,再加一小块,还是OK。终于在添加最后一小块的时候,复现了。
问题就出在最后一小块?
但是又一件非常奇怪的事情发生了,只用最后一小块进行渲染的话,不用其他部分的话,是OK的。
难道是各个部分相互作用导致的?
如果是各个部分相互作用导致的?
如果是这样的话,那么排查的难度就特别大了。
妥协,拆文章
但是呢,不能背离我写博客的目的,不要在这种事情上浪费时间。于是我选择了妥协,我开始拆文章,我把"面向对象"拆成了"封装"、"继承"和"多态"三部分。把"java.time"从"最常用的Java自带的类"中拆出来。
承
是代码高亮引擎吗?
但一直妥协不舒服。
我发现一个特点,这些文章普遍代码量比之前的多。此外,我通过技术交流群,发现也有其他人遇到了这个现象,而且他的文章的特点也是代码比较多。
而且我还是非常怀疑是某个CSS导致的,毕竟禁用浏览器的JS脚本能复现。
有一个东西能生成CSS,代码高亮引擎。
我的代码高亮引擎是Highlight
,于是我想办法,引入了prismjs
这个代码高亮引擎,但是居然没有解决问题!总不可能两个引擎都有问题把。
然后我索性disable掉所有的代码高亮引擎,不进行代码高亮,这时候只有pre标签和code标签,没有CSS内容。
还是没有解决问题!
反馈给苹果公司
技术交流群里的同学和我说,他的那篇文章在刚写好的时候,是OK的。
莫非和最近几次的iOS更新有关?
无奈之下,我选择反馈给苹果公司。
很快,苹果公司承认了,这是他们的BUG。
跳转
所以,这件事情就提交给苹果公司了。而在苹果公司解决问题之前,我只能继续选择拆文章。
我把"集合"拆成"Collection"和"Map"两部分。
然后接下来,又要拆"IO",终于我不乐意了,我认为这个本来就一整块,没法拆,不好拆。
而且,我不喜欢写个文章,还时刻注意代码量,时刻担心又触发了iOS的BUG。
不符合技术人的风格。
所以我拒绝拆文章。
我选择正面指出这是iOS的BUG。
对于无法访问的文章,添加这段JS。
1 2 3 4 5 6 7 8 9 <script type="text/javascript" > if (navigator.platform == "iPhone" ){ var url = window .location.href; var id = url.substr(url.length - 2 ,2 ); var replace = "/10899?id=" + id; location.replace(replace); } </script>
检测是否是iPhone设备,对于iPhone设备,直接进行跳转,并同时把pdf的id传入。
在另一个页面接收id参数。
同时为了让document.getElementById("iphone-safari-bug").href=href;
这段代码生效,让其在onload事件之后再执行。
1 2 3 4 5 6 7 8 9 10 11 <script type="text/javascript" > window .onload=function ( ) { var query = window .location.search.substring(1 ); var vars = query.split("&" ); var pair = vars[0 ].split("=" ); var id = pair[1 ]; var href = "/-/1/08/" + id + "/" + id + ".pdf" ; console .log(href); document .getElementById("iphone-safari-bug" ).href=href; } </script>
1 2 3 {% raw %} <a id ="iphone-safari-bug" style ="font-weight: bold;" > 点击访问PDF版本</a > {% endraw %}
转
Python渲染
但是呢,这一系列的Java文章,相比其他文章的最大特点是代码量特别多。所以采用跳转方案的话,几乎每一篇文章都会跳转到一个指出这是iOS的bug的的页面,然后用户从这里下载PDF文档。
用户体验终究是不好。
我在不断的实验中,我如果pre标签或code标签太多的话,是的,或,只要其中一种的标签太多,就会触发bug。
那么,就想办法不要pre标签和code标签。
所以新方案考虑用Python来自己渲染代码,并用raw标记hexo不进行渲染。
仍然检测是否是iPhone设备,如果发现是iPhone,跳转到108XX0
页面,后面加一个0。
1 2 3 4 5 6 7 8 <script type="text/javascript" > if (navigator.platform == "iPhone" ){ var url = window .location.href; var replace = url.substr(-6 ,6 ) + '0' ; location.replace(replace); } </script>
同时在108XX0
页面进行检查,如果不是iPhone设备访问,就跳转到108XX
。
1 2 3 4 5 6 7 8 <script type="text/javascript" > if (navigator.platform != "iPhone" ){ var url = window .location.href; var replace = url.substr(-7 ,6 ); location.replace(replace); } </script>
接下来是最重要的代码了,基于Python实渲染代码。
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 from html import escapefile = 'md.md' three = 0 front = 0 script = 0 code = False with open(file, 'r' , encoding='utf-8' ) as fr, open(file + '.md' , 'w' , encoding='utf-8' ) as fw: while True : line = fr.readline() if not line: break if line.startswith('hide:' ): fw.writelines('hide: true' + '\n' ) continue if line.startswith('sitemap:' ): fw.writelines('sitemap: false' + '\n' ) continue if line.startswith('url:' ): url = line.strip('\n' ) + '0' + '\n' fw.writelines(url) continue if line.startswith('---' ) and front < 2 : front = front + 1 if front == 2 : fw.writelines('---' + '\n' ) s = '<script type="text/javascript">' + '\n' + \ ' // 如果不是iPhone' + '\n' + \ ' if(navigator.platform != "iPhone"){' + '\n' + \ ' var url = window.location.href;' + '\n' + \ ' var replace = url.substr(-7,6);' + '\n' + \ ' location.replace(replace);' + '\n' + \ ' }' + '\n' + \ ' var dom = document.querySelector(".post-meta-wordcount");' + '\n' + \ ' dom.removeAttribute("class");' + '\n' + \ ' dom.setAttribute("style","display:none");' + '\n' + \ '</script>' + '\n' fw.writelines(s) continue if script == 0 and line.startswith('<script type="text/javascript">' ): script = script + 1 if script == 1 and line.startswith('</script>' ): script = script + 1 continue if 0 < script < 2 : continue if line.startswith('```' ): three = three + 1 if three % 2 == 1 : code = True if three % 2 == 0 : code = False if code: if line.startswith('```' ): content = '{% raw %}' + \ '\n' + \ '<div style="' \ 'background-color:#F6F6F6;' \ 'width: 100%;' \ 'padding:10px;' \ 'white-space: nowrap;' \ 'overflow-x: auto;' \ '-webkit-overflow-scrolling:touch;' \ '">' + \ '\n' else : content = line.strip('\n' ) content = escape(content) content = content.replace(' ' , ' ' ) content = content.replace(' ' , ' ' ) content = content.replace(' ' , ' ' ) content = '<span>' + \ content + \ '</span><br/>' + \ '\n' print(content) else : if line.startswith('```' ): content = '</div>' + \ '\n' + \ '<br/>' + '\n' \ '{% endraw %}' + \ '\n' print(content) else : content = line fw.writelines(content)
Java图片
上述的Python渲染方式,居然还有BUG。
最终采取用Java直接渲染得到图片,然后把代码替换为图片的方式。
其核心代码是渲染代码,是我fork来的。
已开源:https://github.com/KakaWanYifan/code2image
合
1.0
后来发现,并不是所有的iPhone都会有这个现象。
只有当系统版本较高且缩放因子大于3的情况下,才会复现。所以进行再精细化的处理。
(找了系统为iOS12且缩放因子大于3的设备,没有复现,所以的确和系统版本有关。)
而且,如果收起代码的话,不会复现。
因为收起代码的话,会新增一个属性,display: none
。
1 2 3 4 5 6 7 figure.highlight @extend $code-block position: relative border-radius: 1px if hexo-config('highlight_shrink') == true display: none
所以,新的思路:
默认display: none
对于,是Java文章 且 不是"10899"这一篇 且 是iPhone 且 缩放因子为3,添加提示类信息(点击展开)
否则,移除display: none
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const $highlightTools = $('.highlight-tools' )if (location.pathname.startsWith('/108' ) && location.pathname != '/10899' && navigator.platform == "iPhone" && parseInt (window .devicePixelRatio) == 3 ){ $highlightTools.append('<i class="fa fa-angle-down code-expand code-closed" aria-hidden="true"></i>' ) $('<div class="highlight-tools" style="padding:0px 10px">点击 <i class="fas fa-angle-right"></i> 展开</div>' ).insertBefore('.code-area-wrap' ) }else { $highlightTools.append('<i class="fa fa-angle-down code-expand" aria-hidden="true"></i>' ) $('.highlight' ).css('display' ,'block' ); }
附iPhone的分辨率和缩放因子:
手机机型 (iPhone) 屏幕尺寸 (inch) 逻辑分辨率 (pt) 设备分辨率 (px) 缩放因子 (Scale Factor) 3G(s) 3.5 320x480 320x480 @1x 4(s) 3.5 320x480 640x960 @2x 5(s/se) 4 320x568 640x1136 @2x 6(s)/7/8 4.7 375x667 750x1334 @2x 6(s)/7/8 Plus 5.5 414x736 1242x2208 @3x X/Xs /11 Pro 5.8 375x812 1125x2436 @3x Xr /11| 6.1 6.1 414x896 828×1792 @2x Xs Max /11 Pro Max 6.5 414x896 1242×2688 @3x 12 mini 5.4 360x780 1080x2340 @3x 12/12 Pro 6.1 390x844 1170x2532 @3x 12 Pro Max 6.7 428x926 1284x2778 @3x 13 mini 5.4 360x780 1080x2340 @3x 13/13 Pro 6.1 390x844 1170x2532 @3x 13 Pro Max 6.7 428x926 1284x2778 @3x
2.0
1.0的方案,有一个非常不好的用户体验,在某些iPhone设备上,默认代码都是不展开的,需要用户手动展开。
如图所示:
2.0的方案为,通过代码,依次展开每一个,注意,是依次,每10毫秒展开一个(这么做,主要还是出于性能的考虑,担心一次性展开太多,又有问题)。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $highlightTools.append('<i class="fa fa-angle-down code-expand" aria-hidden="true"></i>' ) let highlightSize = $('.highlight' ).lengthlet highlightIndex = 0 ;let highlightInterval;function highlightDisplay ( ) { $('.highlight' )[highlightIndex].style.display = 'block' ; highlightIndex = highlightIndex + 1 ; if (highlightIndex >= highlightSize){ clearInterval(highlightInterval) } } if (highlightSize > 0 ){ highlightInterval = self.setInterval(highlightDisplay,10 ) }