这篇文章用来记录Web(包括PC和移动端)开发过程中遇到的一些疑难问题的解决方案。
- P1、 '1像素边框'问题
- P2、只读输入框在ios上的莫名表现
- P3、自适应高度的textarea
- P4、 CSS3实现翻牌特效
- P5.IOS 10 以上Safari手机浏览器禁止缩放
- P6 检测浏览器是否为IE
- P7 去掉input[type='number']后面浏览器默认的角标
- P8 将
window.location.relaod
作为setTimeout
的回调函数 - P9 浏览器滚动元素确定
- P10 什么是Plain Object?如何判断对象是Plain Object
- P11 flex最后一行左对齐
- P12 请求按序展示(多个请求,如果一个前面的请求返回的话,不要覆盖后面的请求)
- P13 纯前端实现base64图片下载,兼容IE10+
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
都是跟滚动相关的属性。设置 scrollTop
和 scrollLeft
还可以产生滚动。但是不同的浏览器或者Webkit以及不同版本对于页面滚动元素实现不一样,一些认为滚动元素是body
,一些认为滚动元素是html
,实现标准非常混乱,现在一般认为标准的实现应该是html
元素,可能是浏览器厂商们也觉得现在的页面滚动元素太乱,一会儿body
一会儿html
,于是搞出来document.scrollingElement
这么个东西。根据 MDN 的介绍:document
的 scrollingElement
是一个只读属性,始终指向页面滚动元素,但是只要版本较高的浏览器支持该属性。下面的获取方式能兼容大多数场景。
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()
}