再谈mobile web retina 下 1px 边框解决方案

本文实际上想说的是ios8下 1px解决方案。 1px的边框在devicePixelRatio = 2的retina屏下会显示成2px,在iphone 6 plug 下,更显示成3px。由其影响美感。

还好,时代总是进步的。也许很多人都不知道, 现在IOS8下,已经支持0.5px了。。 那么意味着,在devicePixelRatio = 2下,我们可以使用如下的css代码:

wwdc-hairline

但是在ios7以下,android等其他系统里,0.5px会被显示为0px,即该解决方案需要写hack兼容老旧系统。

三种方案:

1、JS判断UA,是否是ios8+,是的话则输出类名hairlines,为了防止重绘,这段代码加在head里即可。

if (/iP(hone|od|ad)/.test(navigator.userAgent)) {
  var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/),
    version = parseInt(v[1], 10);
  if(version >= 8){
    document.documentElement.classList.add('hairlines')
  }
}

2、JS判断是否支持0.5px边框,是的话,则输出类名hairlines。

if (window.devicePixelRatio && devicePixelRatio >= 2) {
  var testElem = document.createElement('div');
  testElem.style.border = '.5px solid transparent';
  document.body.appendChild(testElem);
  if (testElem.offsetHeight == 1)
  {
    document.querySelector('html').classList.add('hairlines');
  }
  document.body.removeChild(testElem);
}
// This assumes this script runs in <body>, if it runs in <head> wrap it in $(document).ready(function() {   })

相比于第一种方法,这种方法的可靠性更高一些,但是需要把js放在body标签内,相对来说会有一些重绘,个人建议是用第一种方法。

3、服务端做ios版本判断,输出相应的类名

相比于JS的实现,个人更倾向于在服务端完成,这样前端也少几行代码,并且更加可靠。

如在wormhole里的实现(wormhole是nodejs环境下的一个服务端渲染模版的容器)

{{#if($plugins.detector.os.name === "ios" && $plugins.detector.os.version >= 8)}}
    {{set (hairlines = "hairlines")}}
{{/if}}
<html class="{{hairlines}}">

加上类名后,就可以针对该类名写相应的css了。比如:

div{border:1px solid #000}
.hairlines div{border-width:0.5px}

也许你会问,那ios7以下和其他android机下怎么搞?我的建议是:还是维持老样,不去处理,随着时间的推移,我相信最终都会支持0.5 和 0.3 px边框的。

如果硬要兼容,怎么整?方案也有很多,稍微介绍下:

1、通过viewport + REM的方式来兼容。

目前这种兼容方案相对比较完美,适合新项目(老项目改用REM单位成本会比较高)。 淘宝M首页 就是这种方案。

在devicePixelRatio = 2 时,输出viewport

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

在devicePixelRatio = 3 时,输出viewport

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

同时通过设置对应viewport的rem基准值,这种方式就可以像以前一样轻松愉快的写1px了。关于REM布局,可以参考下我上一篇文章 《移动端H5页面之iphone6的适配》

其他方案(该部分内容来源于妙净同学的分享):

2、 transform: scale(0.5)

实现方式

height:1px;
-webkit-transform: scaleY(0.5);
-webkit-transform-origin:0 0;
overflow: hidden;

优点

圆角无法实现,hack代码多,实现4条边框比较闹心

缺点

只能单独使用,如果嵌套,scale的作用也会对包含的元素产生,不想要的影响,所以此种方案配合:after和:before独立使用较多,比如画一个商品的边框四条线,容器的after和before可以画2条线,利用容器的父元素的after、before再画2条线。

.after-scale{
  position: relative;
}
.after-scale:after{
  content:"";
  position: absolute;
  bottom:0px;
  left:0px;
  right:0px;
  border-bottom:1px solid #c8c7cc;
  -webkit-transform:scaleY(.5);
  -webkit-transform-origin:0 0;
}

3、 box-shadow

实现方式

利用css 对阴影处理的方式实现0.5px的效果

底部一条线

-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5);

优点基本所有场景都能满足,包含圆角的button,单条,多条线,

缺点

颜色不好处理, 黑色 rgba(0,0,0,1) 最浓的情况了。有阴影出现,不好用。

参考链接

4、 background-image

实现方式

设置1px通过css 实现的image,50%有颜色,50%透明

.border {
  background-image:linear-gradient(180deg, red, red 50%, transparent 50%),
  linear-gradient(270deg, red, red 50%, transparent 50%),
  linear-gradient(0deg, red, red 50%, transparent 50%),
  linear-gradient(90deg, red, red 50%, transparent 50%);
  background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%;
  background-repeat: no-repeat;
  background-position: top, right top,  bottom, left top;
  padding: 10px;
    }

优点

配合background-image,background-size,background-position 可以实现单条,多条边框。边框的颜色随意设置

缺点

如果有圆角的效果,很sorry 圆角的地方没有线框的颜色。都要写的代码也不少

参考链接

5、 用图片

实现方式

.border-image{
    border-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB5JREFUeNpiPnH8zH/G////MzAxAAHTyRNn/wMEGABpvQm9g9TJ1QAAAABJRU5ErkJggg==") 2 0 stretch;
border-width: 0px 0px 1px;
}

优点

缺点

也可以通过修改图片来达到圆角的效果,但是由于图片的原因,压缩过后的图片边缘变模糊了(不放大的情况下不明显),需要引用图片或者base64,边框颜色修改起来不方便。

转自:http://www.tuicool.com/articles/ZRv6bun

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="format-detection" content="telephone=no" /> <title>逍遥网络</title> <!-- Favicon --> <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎮</text></svg>"> <style> /* 重置 */ * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; background: linear-gradient(45deg, #0f1a2b, #1a2e4d); color: white; min-height: 100vh; padding: 20px 15px 40px; line-height: 1.6; overflow: hidden; } /* 动态星空背景(纯CSS) */ body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at 20% 30%, rgba(60, 100, 255, 0.1) 0%, transparent 30%), radial-gradient(circle at 80% 70%, rgba(255, 100, 150, 0.1) 0%, transparent 30%), radial-gradient(circle at 50% 10%, rgba(100, 200, 255, 0.08) 0%, transparent 25%); z-index: -2; } /* 毛玻璃遮罩层 */ body::after { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(10, 20, 40, 0.6); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: -1; } h2 { text-align: center; color: #00d2ff; font-size: 30px; margin: 30px 0 10px; text-shadow: 0 0 5px rgba(0, 210, 255, 0.5), 0 0 15px rgba(0, 210, 255, 0.3); font-weight: 700; letter-spacing: 1px; animation: glow 2s infinite alternate; } @keyframes glow { from { text-shadow: 0 0 5px rgba(0, 210, 255, 0.5); } to { text-shadow: 0 0 15px rgba(0, 210, 255, 0.8), 0 0 20px rgba(0, 210, 255, 0.4); } } .subtitle { text-align: center; color: #aaa; font-size: 13px; margin-bottom: 30px; opacity: 0.8; } .form { max-width: 400px; margin: auto; background: rgba(30, 50, 80, 0.25); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border-radius: 20px; padding: 24px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4), inset 0 0 10px rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.18); position: relative; overflow: hidden; } /* 表单底部微光条 */ .form::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, transparent, #00d2ff, transparent); transform: scaleX(0.5); animation: shine 3s infinite alternate; } @keyframes shine { to { transform: scaleX(1); } } .service-link { display: block; margin: 0 0 16px; padding: 16px; text-decoration: none; border-radius: 14px; font-weight: 600; font-size: 16px; color: white; transition: all 0.3s cubic-bezier(0.2, 0, 0.2, 1); position: relative; overflow: hidden; background: rgba(255, 255, 255, 0.08); border-left: 5px solid transparent; transform: translateZ(0); } /* 根据 data-type 设置专属渐变背景 + 边框光晕 */ .service-link[data-type="local"] { --icon-color: #ff7676; background: linear-gradient(135deg, #1e3c7222, #2a529822); box-shadow: 0 0 6px rgba(255, 150, 150, 0.2); } .service-link[data-type="online-game"] { --icon-color: #64c8ff; background: linear-gradient(135deg, #0f202722, #203a4322, #2c536422); box-shadow: 0 0 6px rgba(100, 200, 255, 0.2); } .service-link[data-type="cdk"] { --icon-color: #ffd966; background: linear-gradient(135deg, #3a7bd522, #3a607322); box-shadow: 0 0 6px rgba(255, 200, 100, 0.2); } .service-link[data-type="test"] { --icon-color: #b066ff; background: linear-gradient(135deg, #5d26c122, #9a00d722); box-shadow: 0 0 6px rgba(180, 100, 255, 0.2); } .service-link[data-type="download"] { --icon-color: #00d2b4; background: linear-gradient(135deg, #00b4db22, #0083b022); box-shadow: 0 0 6px rgba(0, 200, 180, 0.2); } /* 悬停放大 + 阴影加深 + 投影外扩 */ .service-link:hover { transform: translateY(-3px) scale(1.03); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3), 0 0 15px var(--hover-color, rgba(255, 255, 255, 0.3)); } /* 按压反馈 */ .service-link:active { transform: scale(0.98); opacity: 0.8; } /* 自定义图标容器 */ .service-link .icon { width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center; position: relative; margin-right: 12px; flex-shrink: 0; } /* 圆形背景光晕 */ .service-link .icon::before { content: ''; position: absolute; width: 100%; height: 100%; background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%); border-radius: 50%; z-index: 0; pointer-events: none; } /* 图标文字(使用 Unicode 字符或 SVG) */ .service-link .icon span { font-size: 20px; position: relative; z-index: 1; color: white; text-shadow: 0 0 4px rgba(255, 255, 255, 0.6); } /* 脉冲光圈动画(悬停时出现) */ .service-link .icon::after { content: ''; position: absolute; width: 44px; height: 44px; border: 2px solid var(--icon-color); border-radius: 50%; opacity: 0; transform: scale(0.8); animation: pulse-ring 2s infinite; z-index: -1; pointer-events: none; } @keyframes pulse-ring { 0% { transform: scale(0.8); opacity: 0; } 50% { transform: scale(1.2); opacity: 0.6; } 100% { transform: scale(0.8); opacity: 0; } } /* 默认隐藏脉冲圈,悬停时显示 */ .service-link .icon::after { animation-play-state: paused; } .service-link:hover .icon::after { opacity: 1; animation-play-state: running; } .service-link span.name { vertical-align: middle; } .service-link .status { float: right; font-size: 11px; padding: 3px 8px; border-radius: 10px; background: rgba(0, 0, 0, 0.3); color: #ddd; margin-top: 6px; transition: all 0.3s ease; } .status.online { background: rgba(0, 180, 0, 0.5); color: #fff; box-shadow: 0 0 6px rgba(0, 200, 0, 0.3); } .status.offline { background: rgba(180, 0, 0, 0.5); color: #fff; box-shadow: 0 0 6px rgba(200, 0, 0, 0.3); } .status.loading { background: rgba(100, 100, 100, 0.4); color: #fff; animation: pulse 1.5s infinite; } @keyframes pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } } </style> </head> <body> <h2>✨ 逍遥网络</h2> <p class="subtitle">一站式入口</p> <div class="form"> <a href="" class="service-link" data-port="" data-type="local" target="_blank" rel="noopener noreferrer"> <span class="icon"><span>🔧</span></span> <span class="name">游戏 活动</span> <span class="status loading">检查中...</span> </a> <a href="https://fafa.krific.cn" class="service-link" data-url="https://fafa.krific.cn" data-type="online-game" target="_blank" rel="noopener noreferrer"> <span class="icon"><span>🎮</span></span> <span class="name">游戏 商城</span> <span class="status loading">检查中...</span> </a> <a href="https://sdk.urptwx.cn" class="service-link" data-url="https://sdk.urptwx.cn" data-type="cdk" target="_blank" rel="noopener noreferrer"> <span class="icon"><span>⚙️</span></span> <span class="name">CDK 兑换</span> <span class="status loading">检查中...</span> </a> <a href="" class="service-link" data-port="" data-type="test" target="_blank" rel="noopener noreferrer"> <span class="icon"><span>🧪</span></span> <span class="name">新手 教程</span> <span class="status loading">检查中...</span> </a> <a href="" class="service-link" data-port="" data-type="download" target="_blank" rel="noopener noreferrer"> <span class="icon"><span>📊</span></span> <span class="name">安卓+IOS+PC 游戏下载</span> <span class="status loading">检查中...</span> </a> </div> <script> function checkLocal(port) { return new Promise((resolve) => { const img = new Image(); img.src = `http://127.0.0.1:${port}/favicon.ico?r=${Date.now()}`; img.onload = () => resolve(true); img.onerror = () => resolve(false); setTimeout(() => resolve(false), 3000); }); } async function checkOnline(url) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); await fetch(url, { method: 'HEAD', mode: 'no-cors', signal: controller.signal }); clearTimeout(timeoutId); return true; } catch (err) { return false; } } document.querySelectorAll('.service-link').forEach(async (link) => { const statusSpan = link.querySelector('.status'); let isOnline = false; if (link.dataset.port) { isOnline = await checkLocal(link.dataset.port); } else if (link.dataset.url) { isOnline = await checkOnline(link.dataset.url); } statusSpan.classList.remove('loading'); statusSpan.textContent = isOnline ? '在线' : '离线'; statusSpan.classList.add(isOnline ? 'online' : 'offline'); }); </script> </body> </html> 在此基础上 优化 安卓 IOS PC 检测分辨率自动适配 完整写出
最新发布
10-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值