Web开发疑难问题解决方案-(最近更新:2019-04-30)

这篇文章用来记录Web(包括PC和移动端)开发过程中遇到的一些疑难问题的解决方案。

P1、 '1像素边框'问题

在平时的开发过程中,经常会遇到UI给的设计稿中元素的边框为0.5px,这里的0.5px指的是CSS像素,对于设备像素比为2的屏幕来说,对应的物理像素就是1个像素,也就是说设计稿的本意是让我们实现真实的占据1个物理像素点的边框。由于直接设置对应CSS属性为0.5px在各平台和浏览器中兼容性很不好,网上给出了很多解决方案,这里仅列出最常用的被论证有效的解决方案。

1.根据设备像素比transform缩放,常借助伪元素

div {
    position: relative;
}
div::after {
    content: '';
    position: absolute;
    z-index: 999;
    top: 0;
    left: 0;
    border: 1px solid red;
    transform-origin: 0 0;
}
@media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){
    div::after{
            width: 200%;
            height: 200%;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            /*border-radius: $radius * 2;有圆角*/
    }
}
@media (-webkit-min-device-pixel-ratio:3),(min-device-pixel-ratio:3){
    div::after{
            width: 300%;
            height: 300%;
            -webkit-transform: scale(.33333);
            transform: scale(.33333);
            /*border-radius: $radius * 3;有圆角*/
    }
}
/**
 * @module 背景与边框
 * @description 为元素添加边框(包括1px边框)
 * @method border
 * @version 2.0.0
 * @param {String} $border-width 指定边框厚度(单位为px),默认值:1px,取值与`border-width`属性一致,不同方向代表边框位置 <2.0.0>
 * @param {String} $border-color 指定边框颜色 <2.0.0>
 * @param {String} $border-style 指定边框样式 <2.0.0>
 * @param {String} $radius 指定边框圆角半径,默认值:null <2.0.0>
 */
 @mixin border($border-width: 1px, $border-color: map-get($base, border-color), $border-style: solid, $radius: null,$opacity: null) {
    // 为边框位置提供定位参考
    position: relative;
    @if $border-width == null {
        $border-width: 0;
    }
    border-radius: $radius;
    &::after {
        // 用以解决边框layer遮盖内容
        pointer-events: none;
        position: absolute;
        z-index: 999;
        top: 0;
        left: 0;
        // fix当元素宽度出现小数时,边框可能显示不全的问题
        // overflow: hidden;
        content: "\0020";
        border-color: $border-color;
        border-style: $border-style;
        border-width: $border-width;
        @if $opacity != null {
            opacity: $opacity;
        }
        // 适配dpr进行缩放
        @media (-webkit-min-device-pixel-ratio:1),(min-device-pixel-ratio:1){
            width: 100%;
            height: 100%;
            @if $radius != null {
                border-radius: $radius;
            }
        }
        @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){
            width: 200%;
            height: 200%;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            @if $radius != null {
                border-radius: $radius * 2;
            }
        }
        @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){
            width: 200%;
            height: 200%;
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            @if $radius != null {
                border-radius: $radius * 2;
            }
        }
        @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:3){
            width: 300%;
            height: 300%;
            -webkit-transform: scale(0.33333);
            transform: scale(0.33333);
            @if $radius != null {
                border-radius: $radius * 3;
            }
        }
        -webkit-transform-origin: (0,0);
        transform-origin:(0 0);
    }
}
div {
    position: relative;
    border: none;
}
div:after {
    content: '';
    position: absolute;
    left: 0;
    background: #000;
    widt
    h: 100%;
    height: 1px;
    transform: scaleY(0.5);/*以dpr = 2为例 */
    transform-origin: 0 0;
}
// 给styled-compoent使用
/**
 * 支持1像素border
 */
function border(border = '1px solid red', radius, opacity) {
  // 为边框位置提供定位参考
  return `position: relative;

  ${radius ? 'border-radius: ' + radius + ';' : ''}
  &::after {
      pointer-events: none;
      position: absolute;
      z-index: 999;
      top: 0;
      left: 0;
      content: "\\0020";
      border: ${border}
      ${opacity ? 'opacity: ' + opacity + ';' : ''}
      @media (-webkit-min-device-pixel-ratio:1),(min-device-pixel-ratio:1){
          width: 100%;
          height: 100%;
          ${radius ? 'border-radius: ' + radius + ';' : ''}
      }
      @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){
          width: 200%;
          height: 200%;
          transform: scale(0.5);
          ${radius ? 'border-radius: ' + radius + ';' : ''}
      }
      @media (-webkit-min-device-pixel-ratio:3),(min-device-pixel-ratio:3){
          width: 300%;
          height: 300%;
          transform: scale(0.33333);
          ${radius ? 'border-radius: ' + radius + ';' : ''}
      }
      transform-origin:0 0;}`
}

参考:
怎么画一条0.5px的边(更新)
再谈Retina下1px的解决方案

P2、只读输入框在ios上的莫名表现

如上左图所示,学校选择输入框是一个只读输入框,整个输入框可点击,包括前面的标签和后面的箭头,点击输入框会隐藏当前页面结构,显示学校选择页面,使用iphone,当点击的区域是input标签区域时,下方会出现输入法的工具条,点击工具条左侧的箭头会弹出键盘,点击完成工具条会隐藏。

<input id="school" readOnly = "readOnly" type="text" placeholder="请选择学校(专科及以上)" />

解决方案
1.使用其他标签模拟一个readOnly输入框;
2.禁止该输入框touchstart或者touchend的默认事件,实践表明禁止click的默认事件不能解决上述问题

$('#school').on('touchend',(e) => {
    e.preventDefault();
    ...
});

P3、自适应高度的textarea

1.设置div标签的contenteditable属性为true,模拟一个输入框,这么做的缺点是不能完全模拟输入框的表现,如focus事件自动弹出键盘

2.使用"同步镜像"
html

<div class="reply-input js-reply-input">
    <pre class="reply-text-mirror js-reply-text-mirror"></pre>
    <textarea class="js-reply-text" placeholder="回复"></textarea>
</div>

css

.reply-input{
    position: relative;
    width: 82%;
    min-height: 92px;
    >textarea{    
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        resize: none;
        outline: none;
        border: 0;
    }
    >.reply-text-mirror{
        display: block;
        white-space: pre-wrap;
        word-wrap: break-word;
        visibility: hidden;
    }
    textarea,.reply-text-mirror{
        line-height: 20px;
        font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
        font-size: 14px;
        padding: 10px 6px 20px 9px;
        min-height: 92px;
        width: 100%;
        border-radius: 4px;
    }
}

javascript

const textarea = $('textarea');
textarea[0].oninput =function() {
    var text = textarea.val();
    mirror.text(text);
}

textarea绝对定位,高度与父元素高度相同100%,通过js操作把输入的字符实时同步到背后的镜像元素来撑高父元素。

P4、 CSS3实现翻牌特效

html

    <div id="js-card-Pop" class="m-card-Pop">
        <div class="front"></div>
        <div class="back"></div>
    </div>

css

.m-card-Pop{
    position: relative;

    .front{
        position:absolute;
        width: 100%;
        height: 100%;
        top:0;
        left:0;
        z-index: 2;        
        backface-visibility: hidden;
    }
    .back{  //翻过来后这一边会朝上
        transform:rotateY(180deg) translateZ(1px) ;
        position:absolute;
        width:100%;
        height:100%;
        top:0;
        left:0;
        backface-visibility: hidden;
        color:#fff;
    }

}
.m-card-Pop-R{
    transition: 0.5s ease-in-out;
    transform-style: preserve-3d;
    transform: rotateY(180deg);
}

javascript

$('#js-card-Pop').addClass('m-card-Pop-R');

参考
https://zhuanlan.zhihu.com/p/27089238

P5.IOS 10 以上Safari手机浏览器禁止缩放

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">

ios 10以上的Safari浏览器即使加上上述声明页面还是可以双指缩放或者双击缩放,https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_0.html,在微信和Chrome浏览器中表现正常。

网友给出的方法

禁止双指缩放

document.addEventListener('touchmove', function (event) {
  if (event.scale !== 1) { event.preventDefault(); }
}, false);

禁止双击缩放

var lastTouchEnd = 0;
document.addEventListener('touchend', function (event) {
  var now = (new Date()).getTime();
  if (now - lastTouchEnd <= 300) {
    event.preventDefault();
  }
  lastTouchEnd = now;
}, false);

这两种方法仅供参考,实践过程中可能与页面本身的事件冲突。

如果仅仅要求禁止双击缩放使用css属性touch-action: manipulation;也是可以的,同时会屏蔽点击事件的300ms延时.

参考
disable viewport zooming iOS 10+ safari?

P6 检测浏览器是否为IE

function isIE() { //ie?
 if (!!window.ActiveXObject || "ActiveXObject" in window)
  return true;
  else
  return false;
 }

P7 去掉input[type='number']后面浏览器默认的角标

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
}
input[type="number"]{
  -moz-appearance: textfield;
}

P8 将window.location.relaod作为setTimeout的回调函数

setTimeout(window.location.reload, 250);

Chrome reports:

Uncaught TypeError: Illegal invocation

需要绑定正确的上下文

setTimeout(window.location.reload.bind(window.location), 250);

P9 浏览器滚动元素确定

scrollTop, scrollLeft, scrollWidth, scrollHeight 都是跟滚动相关的属性。设置 scrollTopscrollLeft 还可以产生滚动。但是不同的浏览器或者Webkit以及不同版本对于页面滚动元素实现不一样,一些认为滚动元素是body,一些认为滚动元素是html,实现标准非常混乱,现在一般认为标准的实现应该是html元素,可能是浏览器厂商们也觉得现在的页面滚动元素太乱,一会儿body一会儿html,于是搞出来document.scrollingElement这么个东西。根据 MDN 的介绍:documentscrollingElement 是一个只读属性,始终指向页面滚动元素,但是只要版本较高的浏览器支持该属性。下面的获取方式能兼容大多数场景。

var rootElement = document.scrollingElement || document.body;

参考
Chrome 中 scrollingElement 的变化

P10 什么是Plain Object?如何判断对象是Plain Object

/**
 * Checks if `value` is a plain object, that is, an object created by the
 * `Object` constructor or one with a `[[Prototype]]` of `null`.
 */
function isPlainObject(value) {
  if (Object.prototype.toString.call(value) != "[object Object]") {
    return false
  }
  if (Object.getPrototypeOf(value) === null) {
    return true
  }
  let proto = value
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  return Object.getPrototypeOf(value) === proto
}

const obj1 = {}
const obj2 = new Object()
const obj3 = Object.create(null)
const obj4 = Object.create({})

function Foo() {
  this.a = 1
}

const obj5 = new Foo()

console.log(
  isPlainObject(obj1), // true
  isPlainObject(obj2), // true
  isPlainObject(obj3), // true
  isPlainObject(obj4), // false
  isPlainObject(obj5)  // false
)

P11 flex最后一行左对齐

"年少无知"的我曾经以为flex布局能够一统天下,无所不能,直到遇到了flex布局最后一行左对齐.

<div id="wrap">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
#wrap {
  display: flex;
  justify-content: space-around;
  flex-wrap: wrap;
}
.item {
  width: 200px;
  height: 100px;
  margin-bottom: 40px;
  background: blanchedalmond;
}

希望实现多列均匀分布的自适应布局,但是最后一行子项不够,导致不能对齐.

目前最好的解决方案都是多添加几个子项,但是由于是自适应的,我们并不知道需要添加多少个子项,通用的做法是添加"足够多"的子项,并且其对后面的布局影响越小越好,

<div id="wrap">
  <div class="item"></div>
  <div class="item"></div>
    ....
    ....
  <div class="item-shim"></div>
  <div class="item-shim"></div>
  <div class="item-shim"></div>
  <div class="item-shim"></div>
  <div class="item-shim"></div>
  <div class="item-shim"></div>
  <div class="item-shim"></div>
  <div class="item-shim"></div>
</div>
....
....
.item-shim {
  width: 200px; /*与item尺寸相同*/
  height: 0;
  /* visibility: hidden; */
}

参考
Flex-box: Align last row to grid

P12 请求按序展示(多个请求,如果一个前面的请求返回的话,不要覆盖后面的请求)

记录每一个请求的发起时间,然后每个请求回来后先判断当前展示在页面上的数据的发起时间,如果本次返回的结果的发起时间晚于当前显示的数据的发起时间,就替换,否则不替换。

(function(){
  var lastShowedResult = 0//当前显示的结果的请求时间,初始为0
  $('input').keyup(function(){
    var requestTime = +Date.now()//每次发请求时记录一下时间,转换成数字,注意在闭包里面
    $.get(url, function(data){
      if (requestTime > lastShowedResult) {//请求拿到后判断是否晚于当前正显示的内容的发起时间,如果是,则
        lastShowedResult = requestTime//替换当前显示结果的发起时间
        showResult(data)//替换页面上的内容
      }
    })
  })
})()

作者:谢然
链接:https://www.zhihu.com/question/49470022/answer/118129515
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

P13 纯前端实现base64图片下载,兼容IE10+

// 这里是获取到的图片base64编码,这里只是个例子哈,要自行编码图片替换这里才能测试看到效果
  const imgUrl = 'data:image/png;base64,...'
  // 如果浏览器支持msSaveOrOpenBlob方法(也就是使用IE浏览器的时候),那么调用该方法去下载图片
  // IE浏览器
  if (window.navigator.msSaveOrOpenBlob) {
    var bstr = atob(imgUrl.split(',')[1])
    var n = bstr.length
    var u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    var blob = new Blob([u8arr])
    window.navigator.msSaveOrOpenBlob(blob, 'chart-download' + '.' + 'png')
  } else {
    // 现代浏览器
    const a = document.createElement('a')
    a.href = imgUrl
    a.setAttribute('download', 'chart-download')
    a.click()
  }

参考
纯前端实现base64图片下载,兼容IE10+

转载于:https://www.cnblogs.com/star91/p/Web-kai-fa-yi-nan-wen-ti-jie-jue-fang-anzui-jin-ge.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值