分类 项目的一些探索和发现 下的文章

Less中calc(100vh - 64px) === 36vh
在使用less的vue项目中,发现calc(100vh - 64px) === 36vh???,原来是先经过less编译处理...

anything inside brackets in the LESS would be calculated by LESS first. So I had to parse the calc as Escape string for LESS

解决:width: ~"calc(100% - 30px)";,注意::这样就不能加变量了

参考:CSS3 calc() in LESS CSS

设计稿只给了PingFangSC-Bold这种苹果字体,但没给font-weight,该怎么做呢?

100 - Thin
200 - Extra Light (Ultra Light)
300 - Light
400 - Regular (Normal、Book、Roman)
500 - Medium
600 - Semi Bold (Demi Bold)
700 - Bold
800 - Extra Bold (Ultra Bold)
900 - Black (Heavy)
参考:常见设计稿字体对应字重font-weight大小

ajax 异步打开新链接被拦截
主要是不是用户自己点击触发的跳转,会被浏览器拦截,看到网上有把ajax变成同步请求来解决的,还有另一种,就是stackoverflow的通过新打开一个页面,然后在重置href来实现:

const target = window.open("","_blank");
ajax().then((data) => {
    target.location.href = data.url;
})

参考链接:jquery window.open in ajax success being blocked

使用了isomorphic-fetch,IE11仍然报Here is no "fetch" on the window env, you need to polyfill it
使用fetch-polyfill的方法,但是明明使用的是isomorphic-fetch,应该已经是polyfill之后的了啊,后在github/fetch中看到:

For use with webpack, add this package in the entry configuration option before your application entry point: entry: ['whatwg-fetch', ...]
于是在webpack入口同样增加:['isomorphic-fetch', ...],也正常了

参考:IE11无法启动,import-html-entry Here is no "fetch" on the window env, you need to polyfill it

记住一次赋值号引起的bug

做leetcode的第二题时,出现一个莫名奇妙的错误:

代码如下:(测试了一下链表的每个节点的值)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if( (l1->val=0 && l1->next==nullptr)) {
            return l2;
        }else if( (l2->val=0 && l2->next==nullptr)) {
            return l1;
        }
        // 测试
        ListNode* temp = l1;
        while(temp !=NULL) {
             cout<<"temp->val: "<<temp->val<<endl;
             temp = temp->next;
        }
        // ...code here
    }
}

样例输入是:[2,4,3],却直接遍历输出了:[0,4,3],查了半天才发现是刚开始的语句就写错了...

记得c的缺陷与陷阱中好像就提过,等号被误用成赋值号,一直没遇到过,这次遇到了,长个记性!

在react的一个评论组件中,大致如下的写法:

render() {
    return (
        <form>
            <div>
                <label htmlFor="username">用户名: </label>
                <input
                  type="text"
                  name="username"
                  value={ this.state.username }
                  onChange={ this.handleOnChange.bind(this, 'username') }
                  required
                ></input>
            </div>
            <div>
                <label htmlFor="commentContent">评论内容: </label>
                <textarea
                  ref={ textarea => this.textArea = textarea }
                  value={this.state.commentContent}
                  onChange={ this.handleOnChange.bind(this, 'commentContent') }
                  name="commentContent"
                  cols="30"
                  rows="10"
                ></textarea>
            </div>
            <button onClick={ this.submitComment.bind(this) }>发布</button>
        </form>
    );
  }

其中的form表单里面使用了原生button元素,神奇的是,每次发表评论后,页面会重新加载,这就很可怕了。在大佬的帮助下,debug的过程略过,结论就是:

在form表单中的button如果没有显示写明type属性为什么,则默认是为type="submit"

- 阅读剩余部分 -

fiddle 抓包:

主要参考:微信内移动前端开发抓包调试工具fiddler使用教程

还有一篇译文:使用Fiddler对IOS系统进行HTTP抓包

Fiddler实现手机抓包——小白入门

fiddle过滤指定链接:只显示我们需要查看的链接情况:

右侧的Filters --> 勾选 use Filters --> 选第二个选项为:show only following hosts ,然后填写你需要监控的hosts,可以填写多个,按分号隔开即可

使用fiddle时发现chrome不断的向google发现数据...

打开chrome,就反复不断的请求clients1.google.com

有说:

在设置中,选高级,可以看到自动填充。
取消“密码和表单”中的“自动填充”功能,即可解决clients1.google.com请求的问题。
这个是谷歌的个人信息同步的请求

楼主的解决办法是:

我的解决办法是设置Filter,在请求上按右键,hide client1.google.com
或者干脆开启全局的Filters,设置 Hide if URL contains 为 client1.google.com

起因遇到一个使用了vantUI框架,但需要定制dialog的需求,所以只能去看vant的transition动画的实现,来满足自己的需求...

查看vant的源码

vant的源码可以在这里看到:https://github.com/youzan/vant/blob/dev/packages/vant-css/src/dialog.css

其中dialog的动效在这:

&-bounce-enter {
    opacity: 0;
    transform: translate3d(-50%, -50%, 0) scale(0.7);
}

&-bounce-leave-active {
    opacity: 0;
    transform: translate3d(-50%, -50%, 0) scale(0.9);
}

这个是用来控制dialog的中间部分的内容弹出的时候的变形动画(..不太好表述),但是试一下就会发现问题,这里没有mask啊,也就是说这里弹出dialog底部的淡黑色的遮罩的动效还得另找...

在公共的css样式中找到了一个fade-in/outhttps://github.com/youzan/vant/blob/dev/packages/vant-css/src/common/animation.css

.van-fade {
  &-enter-active {
    animation: .3s van-fade-in;
  }

  &-leave-active {
    animation: .3s van-fade-out;
  }
}

@keyframes van-fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes van-fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

再平时使用vant的dialog组件时能查看到:

<!-- 未弹出前 -->
<div class="van-dialog" style="z-index: 2003; display: none;">...</div>
<div class="van-modal" style="z-index: 2003; display: none;"></div>

<!-- 弹出后(同时有class的切换) -->
<div class="van-dialog" style="z-index: 2005;">...</div>
<div class="van-modal" style="z-index: 2004;"></div>

可以猜测到,dialog和modal(也就是那个底部的mask)是一种同级组件的关系,而且应该是两个动画,因为同时有class的切换,最后的z-index也说明了这点,因为弹出后是同级关系,所以需要用z-index来控制内容显示在mask之上

使用chrome-devTools的animations工具

以上也只是猜测,想到动画,作为万能的chrome-devTools肯定会有检查动画的工具吧,果然:

google官方-检查动画(需翻墙)

Chrome DevTools谷歌浏览器开发者工具如何检查动画(不好翻墙的可以参考这个)

在devTools的面板里面打开右边三个竖点的菜单,打开more Tools选项,其中的animations就是动画检查工具了

两个用途:

  • 检查动画: 慢速播放、重播或检查动画组的源代码; 动画检查器支持 CSS 动画、CSS 过渡和网络动画,当前不支持 requestAnimationFrame 动画
  • 修改动画: 修改动画组的时间、延迟、持续时间或关键帧偏移,不支持编辑贝塞尔曲线和关键帧

使用:

打开后,直接在页面开始的动画就会被记录下来,而且会被记录为一个个的动画组

动画组的概念:

例如这里是vant的dialog动画被记录下来的两个动画组,我们点击第一个就可以发现,第一个动画组是mask遮罩层的弹出动画,第二个动画组是dialog的弹出动画,而底部还有详细的动画元素,动画过程

控制动画播放速度:

在上述图示的1部分100%, 25%, 10%,就可以进行动画的速度进行更改

在底部的动画帧图中,可以对动画进行

延后动画/改变动画:

在上述图示的4部分,我们可以对动画过程进行控制,通过整体拖动那个扇形区域就可以进行动画的延时,例如这样就可以把mask动画和dialog动画区分的更清晰,通过缩短那个扇形区域可以达到改变动画效果的作用

最终效果

有了工具的帮助,我们可以很准确的判断出有两个动画,而且可以对动画过程进行延时,减速,让我们对整个动画过程有精准的控制

于是我们用两个组件来做,一个mask组件(用来fade-in/out的效果),一个dialog组件(做transform效果)

最终代码如下:

<!-- dialog组件 -->
<template>
    <!--<div>-->
    <transition name="move">
        <div class="dialog" >
            <div class="content">
                <button @click="dispear">关闭</button>
            </div>
        </div>
    </transition>
</template>

<script type="text/ecmascript-6">
    export default {
        data() {
            return {
            }
        },
        methods: {
            dispear() {
                this.$emit('close');
            }
        },
        components: {}
    }
</script>

<style lang="scss" scoped>
    .dialog {
        position: fixed;
        top: 50%;
        left: 50%;
        width: 85%;
        font-size: 16px;
        overflow: hidden;
        transition: .3s;
        border-radius: 4px;
        background-color: #fff;
        transform: translate3d(-50%, -50%, 0);
        z-index: 90;
        .content {
            width: 300px;
            height:200px;
            margin: 0 auto;
        }
        &.move-enter {
            opacity: 0;
            transform: translate3d(-50%, -50%, 0) scale(0.7);
        }
        &.move-leave-active {
            opacity: 0;
            transform: translate3d(-50%, -50%, 0) scale(0.9);
        }
    }
</style>
<!-- mask组件 -->
<template>
    <transition name="van-fade">
        <div class="mask"></div>
    </transition>
</template>

<script type="text/ecmascript-6">
    export default {
        data() {
            return {}
        },
        methods: {},
        components: {}
    }
</script>

<style lang="scss" scoped>
    .mask {
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        opacity: 1;
        background-color: rgba(0, 0, 0, 0.7);
    }
    van-fade {
        &-enter-active {
            animation: .3s van-fade-in;
        }

        &-leave-active {
            animation: .3s van-fade-out;
        }
    }
    @keyframes van-fade-in {
        from {
            opacity: 0;
        }

        to {
            opacity: 1;
        }
    }
    @keyframes van-fade-out {
        from {
            opacity: 1;
        }

        to {
            opacity: 0;
        }
    }
</style>

使用的时候,两个组件同时引用即可,最终效果:

总结:其实一开始没想到是有两个动效的,所以一直也没做出来,之后就放弃了,由于的确有一些弹窗没办法,只能去定制,所以就再次硬着头皮上,没想到原来是有两个transition,加上同级表示来做的(再次说明要善用chrome Devtools),感悟一句:chrome Devtools其实真的是前端入门的地方

注:有缺陷,没有进行scroll事件的防抖、节流处理

某项目需要一下抖音的下拉刷新效果,目前做出来的效果如下:

做的还是很勉强,简而言之:

  1. 手指按住下滑时,需要出现一个旋转的spinner
  2. 随着手指往下滑动,spinner要能跟着旋转
  3. 用户可以取消刷新,取消刷新的动作是再往上滑
  4. 在取消刷新的过程中,spinner需要能跟着倒着旋转回去,在超过一定距离后,即成功取消刷新,spinner消失
  5. 若直接下滑到一定距离,再松开,则开始刷新,此时的spinner开始无限旋转,直到页面刷新完成

spinner怎么做?

也就是一个带缺口的1/4圆怎么做?

参考:mint-ui/mint-spinner

也即:设一个宽高相等的div,再设 border-radius: 50%;,这样就把div变成了一个圆

再设border: 4px solid transparent;,这样就形成了一个圆环,但是这个圆环还是透明的,还看不出效果

1/4圆环的最重要一步就来了:

border-top-color: #f5f5f5;
border-left-color: #f5f5f5;
border-bottom-color: #f5f5f5;

大家可以看出这个缺口是哪一边吗? --对的,就是右边,不过还隐藏了一点就是div默认的background的就是background: transparent;,所以此时显示的圆环就是一个4px宽的背景色为#f5f5f5的1/4圆环

旋转的spinner怎么做?

一个属性搞定:transform: rotate(360deg);,此时表示将元素旋转360度

ransform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜

手指下滑,spinner要能跟着旋转

也是这次的核心:

主要思路: touchstart事件得到用户滑动的开始坐标,touchmove中去判断用户滑动的的位移,touchend事件判断用户滑动结束时的坐标,有了这3对值,就可以进行很多操作了

  • touchstart: 得到startXstartY
  • touchmove: 得到moveXmoveY
  • touchend: 得到endXendY

要手指下滑,spinner跟着旋转,也就是要手指下滑的距离与spinnertransform: rotate(xxxdeg);成比例关系,手指下滑的距离就是moveY-startY可以用diff表示

// 使得转动的角度,随着diff的距离变动
spinner.style.transform = 'rotate('+diff*1.2+'deg)';

这样基本的思路就确定了,但还有一个隐藏的地方,怎么确定用户是在下滑?

参考:模仿抖音的全屏滚动效果

GetSlideAngle(dx, dy) {
    // atan2() 方法可返回从 x 轴到点 (x,y) 之间的角度
    return Math.atan2(dy, dx) * 180 / Math.PI;
},
GetSlideDirection(startX, startY, endX, endY) {
    let dy = startY - endY;
    let dx = endX - startX;
    let result = 0;
    if (Math.abs(dx) < 50 && Math.abs(dy) < 50) {
        return result;
    }
    let angle = this.GetSlideAngle(dx, dy);
    if (angle >= -45 && angle < 45) {
        result = 4;
    } else if (angle >= 45 && angle < 135) {
        result = 1; //向上
    } else if (angle >= -135 && angle < -45) {
        result = 2; // 向下
    } else if (
        (angle >= 135 && angle <= 180) ||
        (angle >= -180 && angle < -135)
    ) {
        result = 3;
    }
    return result;
}

之后在touchmove的时候将,startXstartYmoveXmoveY传入就可以判断此时是否在下滑

用户取消刷新,又倒着上滑的时候

上述的spinner与滑动距离的随动关系,仍可以用,主要是怎么判断用户是在取消刷新?

例如,用户下滑到一定程度,再上滑,但上滑不超过起始点,那么如果一开始用的是startXstartY来判断,此时用户仍会被判断会下滑啊...

解决方法:定义一个maxDown变量来存储用户下滑的最大距离,下滑过程中,touchmove事件会被不断的触发,于是diff的值就会不断被计算出来

// 存储用户下滑的最大距离
if(this.maxDown<diff) {this.maxDown = diff}

// 但用户上滑到一定距离的时候
if(this.maxDown-diff) > this.maxDown*2/3) {
    // 下滑标志置为false
    this.moveDownFlag = false;
    // 隐藏spinner
    this.spinner = false;
}

直接下滑到一定距离,再松开

此时用户触发刷新操作,spinner开始无限旋转,直到页面刷新完成

这里判断用户正常下滑比较容易,只要在touchend事件时,用户是在下滑而且之前的moveDownFlag是为true,这样就可以了

但怎么让spinner无限旋转,而且要充当页面刷新的loading效果呢?

我采用的是vue,所以可以用动态class来实现

vuetemplate部分:

<div class="main__indicator" ref="spinner" :class="{'active': isLoading}" v-show="spinner"></div>

为了性能,这里采用css动画,使用animation+keyframes的组合,CSS @keyframes Rule

sass

@keyframes kebab-spinner-rotate {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);

    }
}
&.active {
    animation:kebab-spinner-rotate 0.8s infinite linear;
}

js:

if(direction === 2 && this.moveDownFlag) {
    this.isLoading = true;
    // 发送ajax更新,此时充当loading效果
    // ajax code ...
    if(response.data.code === 200) {
         this.isLoading = false;
    }
}

但有一个缺陷就是,当spinner无限旋转时与之前的旋转角度有点衔接补上。。。

隐藏彩蛋,固定/绝对定位的水平居中问题

需要注意的是,spinner是固定定位的,而从一开始的预览图中可以看出,它是水平居中的,怎么实现固定定位的水平居中呢?

我的方法是参考的网上的,具体出处有点不清楚了...

left: 50%;
height: 30px;
width: 30px;
margin-left: -15px;