我的技术和生活

  • 一个超级诡异的 iOS Safari `position: fixed` 失效问题

    一个超级诡异的 iOS Safari `position: fixed` 失效问题

    今天前同事李某找我咨询 Hybrid 开发的问题,想起来大前天搞这个问题搞了一天,赶紧记下来,省得忘记。

    先说需求。东家让我做个日历组件,在手机 Web 上用。组件的样式是这样的,很多地方都可以见到,比如南航国航的客户端。

    日历控件需求图

    看起来并不复杂,事实上也是,基本上顺顺利利的开发完成,准备交付。这里有个伏笔,开发中我按老习惯,使用桌面 Chrome,和实际生产环境不太一样。不过我自然要去真机上测试,结果一测问题就出来了。

    因为组件需要全屏展示,所以我设置了如下的CSS:

    .date-picker {
      position:fixed;
      top:0;
      left:0;
      right:0;
      bottom:0;
      background-color: white;
      z-index:1024;
    }
    

    同时,对原本的 <input name="date">,我给它加上 readonly,避免弹出虚拟键盘。理论上,这样的就可以了。但实测时,不滚屏的时候,组件弹出时尺寸是准确的,盖满全屏;然则一旦滚屏,组件就会占据从页面最上方到当前最下面这截位置。大约相当于 position:absolte;top:0 的效果。

    Safari 截图
    手机截图
    如图,可以看到组件占据了全屏,但实际是从页面最上面开始的,定位有问题。用桌面 Safari 调试也可以看出来它的高度是 968,远大于正常的 667。

    这很诡异,上下左右全为0,是上古巨兽 IE6 都支持的做法。iOS Safari 虽然 Bug 多多,不应该连这个都有毛病啊。以 ios safari position fixed 为关键词 Google 之,结果 iOS Safari 历史不清白,当年 iPhone 刚出的时候的确有定位问题,于是虽有满屏的结果,但都不适用。

    然后我想到找其它库,比如 Bootstrap,它的 Modal 组件也是类似的效果。但是怎么测都正常,于是我只好一个样式一个样式修改,仍然没有结果。

    时间慢慢流逝,转眼已经凌晨2点了,就在我几欲放弃之际,突然发现,虽然组件弹出的时候定位有问题,但只要我点掉下面的完成,定位就会立刻恢复正常。

    手机截图
    注意,就是那个“完成”。

    问题至此已经明朗:在 iOS Safari 里,即使 <input> 设置了 readonly,它仍然可以获取输入焦点。获取输入焦点之后,虽然没有弹出虚拟键盘,但仍然是待输入状态。

    此时页面各种交互都是正常工作的,比如点击、滚屏。唯独 position:fixed 定位有问题。点击“完成”离开输入状态,Safari 自动刷新页面元素,定位就正常了。

    于是我在组件弹出后,自动 input.blur(),使其失去焦点,组件的尺寸便正常无误了。


    总结

    移动端 Web 开发总有各种各样稀奇古怪的问题。有些好解决,有些不好解决,比如这个问题,很难定位:

    1. 历史不清白,搜也搜不到
    2. 组件要求全屏,需要避免虚拟键盘,所以会改变默认行为
    3. 其它情况下都是好的

    我能想到的方案,就是想办法,用所有能用的工具,排除掉所有其它问题,最终还是能搞出来的。

  • 二次游日本

    二次游日本

    其实上次的游记还没写完……

    最早动来日本的念头是前几年,人民币汇率喜人,大家冲到日本买买买。不过那时候前司业务蒸蒸日上,确实也没有太多时间,拖着拖着拖到2015年,当时觉得时机合适了,还发了招募贴。结果年底查出来糖尿病,老婆脖子上也发现个瘤子,于是自然就放弃了。

    后来经过大半年的治疗调理,我俩都算渡过难关。于是国庆期间,去了九州。

    前两天种种因缘际会,被我发现海航往返札幌840的特价机票,于是没忍住,剁手,便有了这次旅行。


    这次旅行用的基本是2015年做的攻略,没料到赶上札幌雪祭、日本国庆节,酒店非常难定,机票省下的那点钱全交给酒店了。不过反过来说,确实住得不错,比九州那趟强多了。房间基本摆的开行李,大多数都有专门的温泉,泡汤泡得非常舒服。

    北海道的冬天没有想象中的冷。也许是两年多没在北京,有点忘记北方冬天的感受,衣服带太厚,其实根本用不上。这边的雪不是一般多,每天都在下,有时候多有时候少,下一会儿就放晴,过一会儿又是鹅毛大雪纷飞。可能正是因此才会有“粉雪”的美誉吧。

    小朋友似乎还太小,只爱疯玩傻玩,换言之,找坨雪,就够他玩 high 了。反倒是我们细心安排的行程,比如逛动物园、看雪灯,他觉得兴趣索然,无法全身心享受。让我有些失望


    玩耍过程中,心心念的,却是上班。如今离职2个半月,回顾刚离职时定下的计划,完成的不到一半,让我不禁对前途有些忐忑。出来玩很开心,但是玩耍中处处需要钱——还需要不少钱。想到将来更多想去的地方,不由得暗暗怀念起有班上的日子。

    回去之后好好工作,嗯嗯。

  • 七牛 Node SDK 会导致 Electron 启动新实例

    七牛 Node SDK 会导致 Electron 启动新实例

    如题,暂时不确定是哪里导致的。

    总之,在 Electron 的 main process 里调用七牛云 SDK qiniu.io.putFile(),会启动一个新实例,原本的上传会暂停。这个时候关掉新实例,上传会继续。当前文件上传完成后,下个文件又会启动一个新实例。如此反复。

    文档中的代码如下:

    qiniu.io.putFile(uptoken, key, localFile, extra, function(err, ret) {
          if(!err) {
            // 上传成功, 处理返回值
            console.log(ret.hash, ret.key, ret.persistentId);       
          } else {
            // 上传失败, 处理返回代码
            console.log(err);
          }
    });
    

    已开 issue

    估计要等春节后修复了。

    暂时可以用社区版 SDK 先顶上。

  • Electron + Vuex 导致视图无法自动刷新的问题

    Electron + Vuex 导致视图无法自动刷新的问题

    前几天开发 Meart 的过程中,碰到一个棘手的问题:

    1. 修改数据,视图不变
    2. 切换视图,再回到原来的视图,数据刷新
    3. 通过 Vue devtool 查看 Vuex,有数据提交
    4. 点击最新提交点,或者 “commit all”,之后所有操作正常

    因为我对 Electron 和 Vue 都不是很了解,所以这个问题困扰我很长时间。我尝试了各种办法,包括 .splice()let data = this.data; this.data = data;,但是都没有效果。后来实在没办法了跑到 Vue 论坛里发帖求助

    没想到很快就得到回复,对方虽然没用过 Electron,但答案看起来方向是对的:remote.getGlobal('var') 得到的对象不是一般意义上的对象,Vue 没有办法给它加上 getter/setter,所以无法实现响应式。

    我尝试了一下果然如此,用 require('data.json') 替换从主线程中取值,就一切问题都解决了。或者把 .getGlobal('data') 取出来的值,深度 Object.assign({}, source) 一下也可以。

    看来有必要补一下 ES5 里面 Object 新增函数的知识了。

  • Google Photo 2016

    Google Photo 根据我去年的照片自动生成的视频。感觉还是快乐的日子比较多嘛。

  • FontAwesome 通过组合创建新图标

    FontAwesome 通过组合创建新图标

    FontAwesome 提供了很多好看的图标,使用 WebFont 嵌入页面更是简单又好用,所以我基本上一直用它。不过有时候还是觉得不太够用,这就需要复合使用多个图标。

    下面是个例子,我在图标的右下角增加一个圆形加号,表示增加、创建。

    See the Pen add icon by Meathill (@meathill) on CodePen.

    这里有两个选择:

    1. FontAwesome 提供了一个堆叠图标的样式:fa-stack,可以堆叠任意多的图标。
    2. 因为它实际上只占用了 :before,所以也可以使用 :after 来容纳新增的图标。如果只需要两个图标,这个方式更简单。
  • 第四次游台湾

    第四次游台湾

    前天从台北回到广州,准备过年。这是我第四次游台湾,原本计划一家五口一起,后来老婆家里出事,没走成,变成我带二大一小出游。运气不错,大部分行程都符合甚至超过预期,顺顺利利开开心心玩完九天八夜,回到家,总结一下。

    有些朋友问,“你都去几次台湾了?”“为啥老去台湾啊?”,这里也算一并作答。

    风景秀丽

    合欢山

    清水断崖

    台湾有很多秀丽的风景,比如清水断崖的碧蓝海水,合欢山的瑰丽雪峰。这些风景可能说不上举世无双艳压寰宇,但也绝对值得一去,值得一看。

    所谓“宝岛”,绝非浪得虚名,台湾全岛,旅游资源开发到位,无论去到哪里,都可以轻松安排下三四天的行程;如果跑在路上嫌累,也有很多地方可以呆着度过几天闲适的时光。

    软硬件条件上佳

    太鲁阁燕子洞

    路旁的绯野樱

    国内的自然风光当然不逊海外,然而把人文的软硬件都算上,国内就没啥优势了。

    先说硬件,台湾景区公路发达,规格高设施全;园区内道路平整,无障碍设施齐备,厕所干净无异味有卫生纸。文明社会应有尽有。

    再说软件,台湾收费的地方并不多,即使收费,多半也并不昂贵,景区门票几十块,里面基本上不再二次收费。吃饭更是让人安心,价格可能比市区稍贵一些,但绝对在合理范围内,不用担心被坑被宰。

    PS:目前我觉得性价比最低的是阿里山,以及阿里山小火车……

    便宜

    台铁便当

    其实这点才是最重要的……

    大约因为离得近,而且去的人相对较少(尤其是最近),机票相当便宜,单人往返含税从广州香港走大约不到1.5k。

    住的话台北台中大城市不会太便宜,价格基本参考国内同档次的各级酒店,丰俭由人;旅游区多半住民宿,价格就很亲民了,400、500就能住的不错,700、800就很好了,愿意多花点钱的话,1000+可以住非常大或者非常有特色的房间,配以非常漂亮的窗外景色。

    吃饭也不贵,低端盒饭十几块,高端点的20、30就能吃饱,而且菜品质量我觉得要更胜国内。喜欢吃就换着花样找不同饭店去尝试,大可以放心点菜,我觉得价格远低于国内。

    自由,多元

    补习班的野望

    考试和补习班是海峡两岸学子共同的命运

    与国内各种管制不同,台湾是自由社会,尤其在文化方面。我去台湾必做的事情之一,便是逛漫画书店。这里要插一句,诚品书店当然值得一逛,不过买过几次书都比较失望,比如会竖排版,或者翻译质量很差,所以我还是更喜欢杂志疯这种主营漫画的书店。

    因为自由,所以就不必摆出道貌岸然的样子去扫黄,所以就会有18x的书架,每每令人流连……不过都好贵啊……

    其他

    城隍庙灯市

    我老婆喜欢去买衣服,貌似比国内便宜,而且服务很好,售货员会很认真的帮你挑选、搭配衣服,所以一不小心就买很多。

    我大姨子每年去龙山寺许愿求签还愿。说到这个求签啊,目前求了几次都很灵,很灵很灵,全中。

    我爸感慨:“国外月亮确实比较圆啊……”

    我妈:“还是有钱好啊,可以出来玩。”


    好,大概这么多。未来,我想还会保持一年一次的频率。有兴趣去台湾的朋友可以联系我,我可以帮忙推荐路线和包车司机。


    一些碎碎念

    过年了,吃好喝好

    对比国庆去日本,台湾还是性价比更高,订酒店的时候就各种觉得便宜。蔡英文上台后,与大陆互相看不顺眼,造成陆台关系紧张,直接影响了陆客前往台湾旅游。打车的时候,出租车司机都在骂。

    这个世界,仍然在按照自己的规律发展。人类的道德,主观期望,在这规律面前,不值一提。所以即使我喜欢台湾,我也不得不承认,大陆更有希望;游玩几日之后,必须回到大陆,继续忍受糟糕的空气、脑残的司机、脏兮兮的环境,一边尽力工作,想方设法把生活过得更好。

    希望台湾能一直坚持这样美好下去。希望大陆能学习改造,变得美好。

  • node.js 复制文件最快的方法

    node.js 复制文件最快的方法

    Subway

    最快的方法

    var fs = require('fs');
    
    fs.createReadStream('test.log').pipe(fs.createWriteStream('newLog.log'));
    

    改进使其可以接受回调

    function copy(source, to, callback) {
      var read = fs.createReadStream(source);
      source.on('error', function (err) {
        done(err);
      }
    
      var write = fs.createWriteStream(to);
      write.on('error', function (err) {
        done(err);
      }
      write.on('finish', function () {
        done();
      }
      read.pipe(write);
    
      function done(err) {
        if (err) {
          throw err;
        }
        callback();
      }
    }
    

    继续添加 Promise,并且用 ES2015 的写法

    function copy(source, to) {
      return new Promise( resolve => {
        let read = fs.createReadStream(source);
        source.on('error', err => {
          throw err;
        });
    
        let write = fs.createWriteStream(to);
        write.on('error', err => {
          throw err;
        });
        write.on('finish', ()=> {
          resolve();
        });
    
        read.pipe(write);
      });
    }
    
    // example
    copy('a.txt', 'b.txt')
      .then( () => {
        console.log('copy success');
      })
      .catch( err => {
        console.log('copy error: ', err);
      });
    

    来源:StackOverflow

    Fastest way to copy file in node.js

  • Bootstrap 4 发布 alpha 6

    Bootstrap 4 发布 alpha 6

    这两天直播写 Meart 有点上瘾,博客好久没更了,公更私更都没更。刷推看到这则公告,小更一下吧。
    图文无关,马上又要去台湾了,翻出来一张台湾公安局的照片阵楼。

    Bootsrap 团队宣布 Bootstrap 4 推出 alpha 6 版本

    如题。Bootstrap 4 alpha 已经是第六个版本了,我最近一直在用 alpha 5,感觉没有太大问题。

    这次更新主要包含三个部分:

    1. 拥抱 Flexbox

    这个版本放弃了 IE9,从而可以彻底拥抱 Flexbox(之前的版本中,Grid 多半使用 display:tablefloat 实现,如果要启用 Flexbox,需要单独引用一个 bootstrap-flex.css)。

    根据 CanIuse 显示,目前除了 IE 系列,大部分浏览器均已全面支持 Flexbox。

    这次修改之后,绝大部分组件均已全面转向 Flexbox。

    2. 加强布局和显示样式

    增加了一批用于调整布局和显示的 class,这样从 Bootstrap 3 迁移也会方便一些。

    这块儿我暂时没用到,没啥可说的。

    3. 提升 Grid 布局

    增加了更多的可控样式,可以调整 Grid 布局中的边距。

    4. 升级导航栏

    之前的版本中,导航栏还是个半成品,现在虽然不敢说是“完成版”,单也差不多了。

    这个用途可能比较大,不过还没研究。


    下次再发布就是 beta 版了,感觉离正式推出不远了。

    Staticfile.org 搜索仓库还没更新,文件已经更新了。


    感谢原作者和贡献者的努力,让我们得以用上越来越好用的开源框架,节省大量时间和精力,可以集中于更有产出的事情。


    原文:Bootstrap 4 Alpha 6

  • Hello,2017

    Hello,2017

    春节填许愿坑

    2016年总结

    对数

    2016年基本就一个主题:对数。

    因为前司一个不可明说的愿景,以支持为主要工作的后台组开始了旷日持久的对数工作。然则不对不知道,一对吓一跳,各种数据差的不是一般多,而且原因各种各样,需要找各种人去寻找差异的原因。

    不同数据间差异是一个问题,另一个问题则是如何用服务器统计数据表达业务数据。过去一年我们把各种算法想各种人解释了无数遍,也对很对历史数据进行了调整和重新计算。

    这个过程自新年以使,至我被裁员仍未结束,当然,对于我来说,也算是结束了。我的结论是:

    1. 科学合理的设计数据反馈形式,对公司而言,非常重要
    2. 把财务数据和业务数据用合适的算法对应起来,非常重要

    裁员

    11月被裁员,给我填补上10+年工作的一个空白。被裁之前经常幻想,有补偿金,有竞业禁止,接下来好好休息一下,出去玩一玩,写本书,写个开源产品;被裁之后,才明白那些只是幻想。有几点分享:

    1. N+1 赔偿,N 有上限,当地社平工资的三倍,多出来的部分,看公司
    2. N<=12,多出来的年份,看公司
    3. 1 是上个月工资,有事假病假,相应扣除
    4. 竞业禁止可以不启动
    5. 想要补偿好,一要公司好,二要老板舍得,三,有机会抓点把柄到手里……
    6. 综上,切记不要相信公司,不要相信老板,切记不要相信公司,不要相信老板,切记不要相信公司,不要相信老板

    糖尿病

    病情基本控制住了,于是体重回升了……

    旅游

    终于把日本去了,了却一桩心愿。2017年1月,终于带父母去了趟台湾,了却令一桩心愿。

    下一桩心愿,带爷爷奶奶去日本泡温泉。

    有了小朋友以后,不自觉的就想带他到出去玩,饱览山水。开始假期不够,现在是钱不够了……

    买房

    年初的时候想买房定居,结果落户一年也没落下来,就耽搁了。然后就是房价暴涨,基本又赶不上车了。

    后来想想未必是坏事,如果背着每个月1w+的房贷,被裁时未必能如此淡定坦然,怕是真要撕破脸斗到底了。


    好了,开始制定新一年计划(发现去年的几乎不用改……):

    1. 体重涨回 110KG 了,目标 105。
    2. 每周打一次篮球。
    3. 能够跑下超迷你马拉松,5KM。
    4. 升级博客,建立国内镜像。
    5. 录制若干个系列的教学视频。
      1. 现代化前端开发
      2. 前端中的 TDD
      3. PHP API
    6. 完成几个系列文章
      1. JavaScript 插件模式
      2. Electron 应用开发
    7. 旅行方面,因为今年要换工作,所以就没年假了。去到新公司,也不好上来就请假乱跑,所以年初安排好的就去,后面就不许愿了。
      1. 已经计划好2月5日去北海道,机票单程840,忍不住剁手了
      2. 4月份清迈泼水节,争取吧
    8. 职业方面,有以下几个想法,排名不分先后
      1. 搞一个小公司,做外包/培训/技术孵化
      2. SOHO
      3. 远程加入一家公司
    9. 技术书籍,去年看了2.5本,今年继续吧,目标还是5本
    10. 小说+电影,20本/部
    11. 每周至少写一篇博客
      1. 重写 HTML5 文件 API
      2. 重写导出 Excel
      3. 完成草稿里的几篇文章
    12. 几个 Side Project
      1. 肉大师已经复活
      2. PHP API 工具

    2016 过去了,我很怀念他。

    2017 到来了,希望比去年更好。