虽互不曾谋面,但希望能和你成为笔尖下的朋友
以读书,技术,生活为主,偶尔撒点鸡汤
不作,不敷衍,意在真诚吐露,用心分享
点击右上方,可关注本刊
通过阅读本文,你可以通过css 中overcroll-behavior属性值的设置,处理浏览器溢出滚动,以及禁用移动设备上刷新,下拉滚动时的发光和橡皮圈回弹效果,当然也可以看到css中 Houndini(胡迪宁),也就是css中也可以写变量等知识,如果文有误导的地方,欢迎路过的老师拍砖指正
目录
背景,滚动边界与滚动链接
引入overscroll行为(对应的三个属性值,auto,contain,none)
防止滚动逃离固定位置元素通过overscroll-behavior:contain解决
禁用拉到刷新(overscroll-behavior-y: contain)
禁用超滚色条纹和橡皮筋效果要在滚动边界时禁用反弹效果(橡皮筋效果),使用overscroll-behavior-y: none:
完整Demo
总结
CSS overscroll-behavior
属性允许开发人员在达到内容的顶部/底部时覆盖浏览器的默认溢出滚动行为。使用该案例包括禁用移动设备上的“拉动到刷新”功能,消除过度滚动发光和橡皮筋效果,并防止页面内容在模态/叠加层下滚动
背景
滚动边界和滚动链接
滚动是与页面交互的最基本的方式之一,但是由于浏览器的诡异默认行为,某些UX模式可能很难处理。作为一个例子,带一个应用程序抽屉带有大量用户可能需要滚动的项目。当它们到达底部时,溢出容器将停止滚动,因为没有更多内容可供使用。换句话说,用户到达“滚动边界”。但是请注意,如果用户继续滚动会发生什么情况。抽屉后面的内容开始滚动!滚动由父容器占领;例子中的主页面本身
被证实这种行为称为滚动链接;滚动内容时浏览器的默认行为。通常情况下,默认设置非常好,但有时候这并不理想,甚至不可预料。当用户点击滚动边界时,某些应用可能希望提供不同的用户体验
(在Chrome Android上滚动链接)
拉到刷新效果
拉到刷新是一种直观的手势,通过Facebook和Twitter等移动应用推广。拉下页面并释放,为更新近的帖子被加载。事实上,这种特殊用户体验非常流行,以至于Android这样的移动浏览器都采用了相同的效果。向下滑动页面顶部会刷新整个页面
(左边为原生拉到刷新操作,自定义拉到刷新,右边为原生拉到刷新操作刷新整个页面)
对于像Twitter PWA这样的情况,禁用本地“拉动到刷新”操作可能是有意义的。为什么?在这个应用程序中,你可能不希望用户不小心刷新页面。还有可能看到双刷新动画!另外,定制浏览器的动作可能会更好,并将其与网站的品牌更紧密地对齐。不幸的是,这种类型的定制很难实现。开发人员最终编写不必要的JavaScript,添加非被动触摸监听器(阻止滚动),或者将整个页面粘贴到100vw / vh中(以防止页面溢出)。这些变通办法在滚动性能方面具有良好记录的负面影响
引入overscroll行为
overscroll-behavior
属性是一个新的CSS功能,用于控制当你过度滚动容器(包括页面本身)时发生的情况。你可以使用它来取消滚动链接,禁用/自定义拉动到刷新操作,禁用iOS上的橡皮圈效果(当Safari实现超滚动行为时)等等。最好的部分是,使用overscroll行为不会对页面性能产生负面影响
该属性有三个可能的值
auto
- 默认,源于元素的滚动可能会传播给祖先(父级)元素
contain
- 防止滚动链接。滚动不会传播给祖先,但会显示节点内的本地效果。例如,Android上的滚动滚动效果或iOS上的橡皮筋效果,它会在用户点击滚动边界时通知用户。注意:使用overscroll-behavior:包含html元素可防止超滚动导航操作
none
- 与包含相同,但它也可以防止节点本身内的超滚动效果(例如,Android超量滚动发光或iOS橡皮圈) 注意:overscroll-behavior还支持overscroll-behavior-x和overscroll-behavior-y的简写,如果您只想定义特定轴的行为
让我们深入一些例子来看看如何使用overscroll-behavior
防止滚动逃离固定位置元素
chatbox聊天场景
考虑位于页面底部的固定定位聊天室。其意图是聊天室是独立的组件,它与它后面的内容分开滚动。但是,由于滚动链接,只要用户点击聊天历史记录中的最后一条消息,文档就开始滚动
对于这个应用程序,让chatbox内的滚动内容始终处于聊天状态更为合适。我们可以通过添加超滚动 `overscroll-behavior:contain
行为来实现这一点:包含持有聊天消息的元素
#chat .msgs {
overflow: auto;
overscroll-behavior: contain;
height: 300px;
}
本质上,我们创建了聊天室的滚动上下文和主页面之间的逻辑分隔。最终的结果是当用户到达聊天记录的顶部/底部时,主页面保持放置状态。在聊天框中开始的滚动不会传播出去
(聊天窗口下的内容也会滚动)
页面重叠场景
下面”方案的另一个变动就是是当你看到内容在固定位置叠加后滚动时。一个死的样品overscroll行为是为了!浏览器试图帮助,但它最终使网站看起来越来越多。
示例 - 带和不带过度滚动行为的模态:包含
(左边之前:页面内容在叠加层下滚动,右边之后:页面内容不会在叠加层下滚动)
禁用拉到刷新
关闭pull-to-refresh操作是一行CSS。只要阻止整个视口定义元素的滚动链接。在大多数情况下,这是
body {
/* 禁用“拉到刷新”功能,但允许发生滚动发光效果 Disables pull-to-refresh but allows overscroll glow effects. */
overscroll-behavior-y: contain;
}
通过这个简单的添加,我们修复了聊天框演示中的双拉到更新动画,并且可以实现使用整洁加载动画的自定义效果。收件箱刷新时整个收件箱也会变模糊
(左边为之前,右边为之后)
以下是完整代码的一部分
body.refreshing #inbox {
filter: blur(1px);
touch-action: none; /* 防止滚动 prevent scrolling */
}
body.refreshing .refresher {
transform: translate3d(0,150%,0) scale(1);
z-index: 1;
}
.refresher {
--refresh-width: 55px;
pointer-events: none;
width: var(--refresh-width);
height: var(--refresh-width);
border-radius: 50%;
position: absolute;
transition: all 300ms cubic-bezier(0,0,0.2,1);
will-change: transform, opacity;
...
}
class="refresher">
class="loading-bar">
class="loading-bar">
class="loading-bar">
class="loading-bar">
id="inbox">
let _startY;
const inbox = document.querySelector('#inbox');
inbox.addEventListener('touchstart', e => {
_startY = e.touches[0].pageY;
}, {passive: true});
inbox.addEventListener('touchmove', e => {
const y = e.touches[0].pageY;
// 在容器顶部时激活自定义的拉到刷新效果 Activate custom pull-to-refresh effects when at the top of the container
// 用户正在滚动 and user is scrolling up.
if (document.scrollingElement.scrollTop === 0 && y > _startY &&
!document.body.classList.contains('refreshing')) {
// refresh inbox.
}
}, {passive: true});
禁用超滚色条纹和橡皮条纹效果
要在滚动边界时禁用反弹效果(橡皮筋效果),请使用 overscroll-behavior-y:none:
body {
/* 禁用拉到刷新和过卷滚发光效果。 仍然保持滑动导航Disables pull-to-refresh and overscroll glow effect.
Still keeps swipe navigations. */
overscroll-behavior-y: none;
}
(左边之前:下拉滚动边界显示辉光,右边之后:下拉时辉光禁用)
注意:这仍然会保留左/右滑动导航。为了防止导航,你可以使用overscroll-behavior-x:none
完整Demo
把它放在一起,完整的聊天框演示,使用overscroll-behavior行为来创建一个自定义的拉动到刷新动画,并禁用滚动从转义聊天室小部件。这提供了一个最佳的用户体验,如果没有CSS过度滚动行为
这是示例中HTML结构代码:
关注微信itclanCoder公众号...
class="refresher">
class="loading-bar">
class="loading-bar">
class="loading-bar">
class="loading-bar">
id="inbox">
overflow:auto and uses
overscroll-behavior-y:contain 去阻止这个默认行为.
id="chat-window-template">
id="title">
type="text" placeholder="Enter text">
这是css代码
@charset "UTF-8";
/**
*
* @authors 随笔川迹 ([email protected])
* @date 2018-04-05 01:53:00
* @version $Id$
* @link (https://juejin.im/post/5a005392518825295f5d53c8)
* @weChatPublicId (itclanCoder)
* @QQGroup (643468880)
* @PersonWeChatId (suibichuanji)
* @PersonQQ (1046678249)
* @describe 功能描述 禁用固定位置元素上的滚动链接Demo css样式
*
*
*/
* {
box-sizing: border-box;
}
html {
/*
--是css Houndini,是一套正在到来的css APi,css对变量的支持,允许在css中
声明如--height,--width的自定义属性,而后通过var()函数对变量求值,可以理解为简化版的less/sass,但是它不能通过DOM API存取
*/
--header-height: 60px;
}
html,
body {
font-family: "Open Sans", sans-serif;
font-weight: 300;
font-size: 16px;
background: #fafafa;
margin: 0;
overscroll-behavior-y: contain;
/* 禁用下拉刷新,保持发光效果 disable pull to refresh, keeps glow effects */
}
h1,
h2,
h3 {
margin: 0;
font-weight: inherit;
}
header {
padding: 0 8px;
background: #f44336;
color: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 2;
height: var(--header-height);
display: flex;
align-items: center;
justify-content: space-between;
}
#inbox {
padding-top: var(--header-height);
/* height of header */
}
chat-window {
position: fixed;
bottom: 0;
right: 16px;
}
p {
/*属性设置某个选择器出现次数的计数器的值。默认为 0,利用这个属性,计数器可以设置或重置为任何值,可以是正值或负值。如果没有提供 number,则默认为 0。*/
counter-reset: email;
}
p div {
margin: 0;
padding: 16px 8px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-between;
}
p .label::after {
counter-increment: email;
content: counter(email);
margin-left: 8px;
}
body.refreshing #inbox,
body.refreshing header {
filter: blur(1px);
/*滤镜,1像素模糊*/
touch-action: none;
/* 防止滚动 prevent scrolling */
}
body.refreshing .refresher { /*下拉刷新小图标*/
transform: translate3d(0, 150%, 0) scale(1);
z-index: 1;
visibility: visible;
}
.refresher {
/*
pointer-events即可让这个HTML元素(包括它的所有子孙元素)失去所有的事件响应。鼠标焦点会直接无视它,click、mouseover等所有事件会穿透它到达它的下一级元素
1. 阻止用户的点击动作产生任何效果
2. 阻止缺省鼠标指针的显示
3. 阻止CSS里的hover和active状态的变化触发事件
4. 阻止JavaScript点击动作触发的事件
在许多网站上过节的时候页面最上层会用canvas绘制的雨、雪花,避免这些悬浮物遮挡住页面从而影响鼠标点击,可以使用pointer-events=none属性,让这些上方的canvas不会遮挡鼠标事件,让鼠标事件可以穿透上方的canvas来点击页面
*/
pointer-events: none;
--refresh-width: 55px;
background: #fff;
width: var(--refresh-width);
height: var(--refresh-width);
border-radius: 50%;
position: absolute;
left: calc(50% - var(--refresh-width) / 2);
padding: 8px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2);
transition: all 300ms cubic-bezier(0, 0, 0.2, 1);
will-change: transform, opacity;
/*使用了CSS3 will-change加速创建新的渲染层*/
display: inline-flex;
justify-content: space-evenly;
align-items: center;
visibility: hidden;
}
body.refreshing .refresher.shrink {
transform: translate3d(0, 150%, 0) scale(0);
opacity: 0;
}
.refresher.done {
transition: none;
}
.loading-bar {
width: 4px;
height: 18px;
border-radius: 4px;
animation: loading 1s ease-in-out infinite;
}
.loading-bar:nth-child(1) {
background-color: #3498db;
animation-delay: 0;
}
.loading-bar:nth-child(2) {
background-color: #c0392b;
animation-delay: 0.09s;
}
.loading-bar:nth-child(3) {
background-color: #f1c40f;
animation-delay: .18s;
}
.loading-bar:nth-child(4) {
background-color: #27ae60;
animation-delay: .27s;
}
@keyframes loading {
0% {
transform: scale(1);
}
20% {
transform: scale(1, 2.2);
}
40% {
transform: scale(1);
}
}
这是示例的js代码
/**
*
* @authors 随笔川迹 ([email protected])
* @date 2018-04-05 01:56:33
* @version $Id$
* @weChatPublicId ((itclanCoder))
* @QQGroup ((643468880))
* @PersonWeChatId ((suibichuanji))
* @PersonQQ ((1046678249))
* @link ((https://juejin.im/post/5a005392518825295f5d53c8))
* @describe 禁用固定位置元素上的滚动链js
*/
(() => {
if (!CSS.supports('overscroll-behavior-y', 'contain')) {
alert("Your browser doesn't support overscroll-behavior :(");
}
// 定义自定义元素 Define custom element.
customElements.define('chat-window', class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
const tmpl = document.querySelector('#chat-window-template');
shadowRoot.appendChild(tmpl.content.cloneNode(true));
shadowRoot.querySelector('#title').textContent = this.title;
const closeButton = shadowRoot.querySelector('#close');
closeButton.addEventListener('click', e => {
this.remove();
});
this.msgs = shadowRoot.querySelector('.msgs');
this.input = shadowRoot.querySelector('input');
this.input.addEventListener('keypress', e => {
if (e.code === 'Enter' && this.input.value) {
const msg = document.createElement('div');
msg.classList.add('msg');
msg.textContent = this.input.value;
this.appendChild(msg);
e.target.value = null;
this._scrollToBottom();
}
});
const toolbar = shadowRoot.querySelector('.toolbar');
toolbar.addEventListener('click', e => {
this.open = !this.open;
if (this.open) {
this.input.focus();
} else {
this.input.blur();
this.blur();
}
});
this.tabIndex = 0;
this.open = this.hasAttribute('open');
}
get open() {
return this._open;
}
set open(val) {
this._open = val;
if (this._open) {
this.setAttribute('open', '');
this._scrollToBottom();
} else {
this.removeAttribute('open');
}
}
_scrollToBottom() {
this.msgs.scrollTop = this.msgs.scrollHeight;
}
});
async function simulateRefreshAction() {
const sleep = (timeout) => new Promise(resolve => setTimeout(resolve, timeout));
const transitionEnd = function(propertyName, node) {
return new Promise(resolve => {
function callback(e) {
e.stopPropagation();
if (e.propertyName === propertyName) {
node.removeEventListener('transitionend', callback);
resolve(e);
}
}
node.addEventListener('transitionend', callback);
});
}
const refresher = document.querySelector('.refresher');
document.body.classList.add('refreshing');
await sleep(2000);
refresher.classList.add('shrink');
await transitionEnd('transform', refresher);
refresher.classList.add('done');
refresher.classList.remove('shrink');
document.body.classList.remove('refreshing');
await sleep(0); // let new styles settle.
refresher.classList.remove('done');
}
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function formatDateBasedOnToday(date) {
const today = new Date();
const opts = {};
if (date.getDay() === today.getDay()) {
opts.minute = 'numeric';
opts.hour = 'numeric';
} else {
opts.month = 'short';
opts.day = 'numeric';
}
return new Intl.DateTimeFormat('en-US', opts).format(date);
}
function populatePage(inbox) {
const frag = new DocumentFragment();
let date = new Date();
for (let i = 0; i < NUM_EMAILs; ++i) {
date.setMinutes(date.getMinutes() - getRandomIntInclusive(1, 10));
const div = document.createElement('div');
div.innerHTML = `Email${formatDateBasedOnToday(date)}`;
frag.appendChild(div);
}
inbox.appendChild(frag);
}
const NUM_EMAILs = 100;
let _startY = 0;
const inbox = document.querySelector('#inbox');
inbox.addEventListener('touchstart', e => {
_startY = e.touches[0].pageY;
}, { passive: true });
inbox.addEventListener('touchmove', e => {
const y = e.touches[0].pageY;
// Activate custom pull-to-refresh effects when at the top fo the container
// and user is scrolling up.
if (document.scrollingElement.scrollTop === 0 && y > _startY &&
!document.body.classList.contains('refreshing')) {
simulateRefreshAction();
}
}, { passive: true });
populatePage(inbox);
})();
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-43475701-1', 'ebidel.github.io');
ga('send', 'pageview');
最终示例效果如下所示
(示例效果)
本文主要是针对页面上的滚动,自定义下拉刷新与溢出效果,通过css中的overscroll-behavior:container阻止滚动链接,也就是在触发子元素的事件操作时,不会传递给父级元素,相当于是阻止事件的冒泡,当然阻止滚动链接在页面上有水平方向的,也有垂直方向的,垂直方向上设置overscroll-behavior-y:none:时可在下拉滚动时禁用反弹效果(橡皮筋效果)
当然文中的刷新动画效果是css3的@keyframes的,当然还有解决这种溢出,系统默认滚动条,橡皮筋回弹,以及禁止长按选中文字,选中图片,系统默认菜单,事件点透问题时可以使用document.addEventListener('touchstart',function(ev){ev.preventDefault();})解决
Demo源码地止:https://ebidel.github.io/demos/chatbox.html
原文出处:httpsdevelopers.google.comwebupdates201711overscroll-behaviorutm_source=mobiledevweekly&utm_medium=email)
[](https://mobiledevweekly.com/issues/185
推荐阅读:
Es6中模块(Module)的默认导入导出及加载顺序
Es6中的模块化Module,导入(import)导出(export)
使用相交观察器和SQIP进行渐进式图像加载
使用交叉点观察器延迟加载图像以提高性能
关于社交圈子的一点思考
js中的正则表达式(2)
js中的正则表达式(1)
手势魅力-设置一个触摸菜单
js之工厂构造函数模式
现代web开发方法
理解构造函数与原型对象
作者:川川,一个靠前排的90后帅小伙,具有情怀的代码男,路上正追逐斜杠青年的践行者,愿做你耳朵旁边的枕男,眼睛笔尖下的窗户
随手点赞
手留余香
(个人微信:suibichuanji)