前端魔法堂:屏蔽Backspace导致页面回退

字节筑梦
• 阅读 3391

前言

 前几天用户反映在录入资料时一不小心错按Backspace键,就会直接回退到是一个页面,导致之前辛辛苦苦录入的资料全部丢失了。哦?居然还有这种情况。下面我们来一起探讨一下吧!

Windows系统下独有的行为

 Windows下的IE、FireFox和Chrome 52之前的浏览器,当焦点不在一个可编辑的元素上时,按Backspace键就会回退到上一个页面,按Shift+Backspace键则会前进到下一个页面。
 而Chrome 52以后的浏览器则屏蔽了BackspaceShift+Backspace的上述行为,而是采用Alt+Left实现回退和Alt+Right实现前进。如果想恢复Backspace回退,则需要安装Go Back With Backspace的Extension才行。
 对于FireFox而言,我们可以设置BackspaceShift+Backspace的行为。

  1. 在地址栏输入about:config

  2. 在搜索框输入browser.backspace_action,然后设置项目值即可。有3个可选项
    0,表示BackspaceShift+Backspace的行为对应页面回退和前进(Windows下的默认值)

1,表示BackspaceShift+Backspace的行为对应页面向下滚动和向上滚动
2或其他值,表示不响应BackspaceShift+Backspace(Ubuntu16下的默认值)

注意:Linux和OS X下的浏览器按BackspaceShift+Backspace不会触发页面的回退和前进。

如何应对

方案一:页面跳转时弹出二次确认

 通过beforeunload事件实现页面跳转时弹出二次确认模态窗,让用户有后悔的机会。但会截断其他正常跳转的操作流畅性,在确实没有办法时才使用!

方案二:直接屏蔽

 屏蔽BackspaceShift+Backspace的默认行为,仅当焦点落在可编辑区域中时才暂时取消屏蔽。
那么哪些算是能获得焦点的可编辑区域呢?就下面这些咯!!

input[type=text]:not([readonly])
input[type=password]:not([readonly])
input[type=number]:not([readonly])
input[type=email]:not([readonly])
input[type=url]:not([readonly])
input[type=search]:not([readonly])
input[type=tel]:not([readonly])
textarea:not([readonly])
[contenteditable]:not([readonly])

就是说当焦点落在上述符合规则的元素上时,按BackspaceShift+Backspace的默认行为就不是页面跳转,因此不用屏蔽掉。

附加功能

 现在我们的目的是页面不会因为用户误操作而刷新,导致页面数据丢失。这里有两个组合键同样会的导致页面刷新

  1. ctrl+r刷新当前页面,可被阻止;

  2. ctrl+w关闭当前窗体或标签页,无法阻止。

代码时间.js

    ;window.nobsgb || (function(exports){
                var started = false
                exports.start = function(){started = true}
                exports.stop = function(){started = false}

        var KEYCODE = {
            BACKSPACE: 8,
            R: 82
        }
        // 判断type是否不受阻止
        var isEscapableType = function(rEscapableTypes){
            return function(type){
                return rEscapableTypes.test(type)
            }
        }(/text|textarea|tel|email|number|search|password|url/i)
        // 判断标签是否不受阻止
        var isEscapableTag = function(rEscapableTag){
            return function(tag){
                return rEscapableTag.test(tag)
            }
        }(/input|textarea/i)
        // 判断是否设置为content editable
        var isContentEditable = function(el){
            return el.isContentEditable
        }
        // 判断是否为不受阻止的Backspace
        var isEscapableBackspace = function(el){
            return or(isEscapableTag(el.tagName)
                      && 
                      or(!('type' in el)
                         , ('type' in el) && isEscapableType(el.type) && !el.readOnly)
                      , isContentEditable(el))
        }
        var isCtrlR = function(e, keycode){
            return e.ctrlKey && KEYCODE.R === keycode
        }
        var isArray = function(x){
            return /Array/.test(Object.prototype.toString.call(x))
        }
        
        var getEvt = function(e){
            return e || window.event
        }
        var getTarget = function(e){
            return e.target || e.srcElement
        }
        var getKeycode = function(e){
            return e.keyCode || e.which
        }
        var preventDefault = function(e){
            e.preventDefault && e.preventDefault()
            e.returnValue = false
            return false
        }
        var listen = function(listen){
            return function(evtNames, handler){
                if (!isArray(evtNames)){
                    evtNames = [evtName]
                }
                var i = 0
                  , len = evtNames.length
                for (; i < len; ++i){
                    listen(evtNames[i], handler)
                }
            }
        }(function(evtName, handler){
            if (or(document['addEventListener'] && (document['addEventListener'].apply(document, arguments) || true)
                   , document['attachEvent'] && (document['attachEvent'].apply(document, arguments) || true))){
                document['on'+evtName] = handler
            }
            
        })
        
        var or = function(){
            var ret = false
              , i = 0
              , len = arguments.length
            for (; !ret && i < len; ++i){
                ret = ret || arguments[i]
            }
            return ret
        }
        var handler = function(e){
                        if (!started) return true
            var evt = getEvt(e)
              , el = getTarget(evt)
              , keyCode = getKeycode(evt)

            if (or(KEYCODE.BACKSPACE === keyCode && !isEscapableBackspace(el)
                  , isCtrlR(evt, keyCode))){
                return preventDefault(evt)
            }
        }
        
        listen(['keydown'], handler)
    }(window.nobsgb = {}))

代码时间.cljs

core.cljs

(ns nobsgb.core
  (:require [nobsgb.dom :as dom]
            [nobsgb.pred :as pred]))

(def started false)
(defn ^:export start []
  (set! started true))
(defn ^:export stop []
  (set! started false))

(defn handler
  "keydown事件响应函数"
  [e]
  (when started
    (let [evt (dom/get-evt e)
          el (dom/get-el evt)
          key-code (dom/get-key-code evt)
          ctrl-key (dom/get-ctrl-key evt)
          read-only (dom/get-read-only el)
          type (dom/get-type el)
          content-editable (dom/get-content-editable el)
          tag (dom/get-tag el)]
      (if-not
        (pred/escapable?
          key-code read-only type tag content-editable ctrl-key)
        (dom/prevent-default evt)
        true))))

(defonce init
  (#(dom/listen! js/document "keydown" handler)))

dom.cljs

(ns nobsgb.dom)

(defn get-evt
  [e]
  (if (some? e) e (.event js/window)))

(defn get-el
  [e]
  (let [el (.-target e)]
    (if (some? el) el (.-srcElement e))))

(defn get-key-code
  [e]
  (.-keyCode e))

(defn get-ctrl-key
  [e]
  (.-ctrlKey e))

(defn get-read-only
  [el]
  (-> el (aget "readOnly") js/Boolean))

(defn get-type
  [el]
  (let [type (.-type el)]
    (if (some? tpye) type "")))

(defn get-tag
  [el]
  (.-tagName el))

(defn get-content-editable
  [el]
  (.-isContentEditable el))

(defn prevent-default
  [e]
  (if (some? (.-preventDefault e))
    (do
      (.preventDefault e)
      (set! (.-returnValue e) false)
      false)
    true))

(defn listen!
  [el evt-name handler]
  (cond
    (fn? (.-addEventListener el)) (.addEventListener el evt-name handler)
    (fn? (.-attachEvent el))      (.attachEvent el (str "on" evt-name) handler)
    :else (aset el (str "on" evt-name) handler)))

pred.cljs

(ns nobsgb.pred)
;;;; 断言

(defonce ^:const KEYCODES
  {:backspace 8
   :r 82})

(defn matches-key?
  "是否匹配指定键码"
  [indicated-key-code key-code]
  (= indicated-key-code key-code))

(def ^{:doc "是否为退格键"}
  backspace?
  (partial matches-key? (:backspace KEYCODES)))

(def ^{:doc "是否为字母R键"}
  r?
  (partial matches-key? (:r KEYCODES)))

(defn with-ctrl?
  "是否在按ctrl的基础上按其他键"
  [ctrl-key]
  (or (= ctrl-key "1")
      (true? ctrl-key)))

(defn ctrl+r?
  "是否为ctrl+r"
  [ctrl-key key-code]
  (and (with-ctrl? ctrl-key)
       (r? key-code)))
(def not-ctrl+r? (complement ctrl+r?))

(defn escapable-type?
  "是否为可跳过的type属性"
  [type]
  (some?
    (some->> type
      (re-matches #"(?i)text|password|tel|number|email|search|url"))))

(defn escapable-tag?
  "是否为可跳过的tag"
  [tag]
  (some?
    (some->> tag
      (re-matches #"(?i)input|textarea"))))

(def ^{:doc "是否设置为可编辑元素"}
  content-editable? identity)

(def ^{:doc "是否设置为只读"}
  read-only? identity)
(def writable? (complement read-only?))

(defn escapable-backspace?
  [key-code read-only type tag content-editable]
  (and (backspace? key-code)
       (writable? read-only)
       (or (escapable-type? type)
           (escapable-tag? tag)
           (content-editable? content-editable))))

(defn escapable?
  [key-code read-only type tag content-editable ctrl-key]
  (or
    (and (not-ctrl+r? ctrl-key key-code)
         (not (backspace? key-code)))
    (escapable-backspace? key-code read-only type tag content-editable)))

总结

 尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohn... ^_^肥仔John

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
3年前
sql注入
反引号是个比较特别的字符,下面记录下怎么利用0x00SQL注入反引号可利用在分隔符及注释作用,不过使用范围只于表名、数据库名、字段名、起别名这些场景,下面具体说下1)表名payload:select\from\users\whereuser\_id1limit0,1;!(https://o
Karen110 Karen110
4年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Stella981 Stella981
3年前
Kerberos无约束委派的攻击和防御
 0x00前言简介当ActiveDirectory首次与Windows2000Server一起发布时,Microsoft就提供了一种简单的机制来支持用户通过Kerberos对Web服务器进行身份验证并需要授权用户更新后端数据库服务器上的记录的方案。这通常被称为Kerberosdoublehopissue(双跃点问题),
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Linux下的快捷键整理
20191026   00:37:25今天上网整理了相关资料,希望对大家有用:1.关于终端的快捷键:Tab:tab键是比较常用的一个快捷键,它的作用是补全文件名或者路径。举例来说,输入”cd /ho”在按一下tab键,终端里就会显示”cd /home”了。如果您的文件夹下,有两个名字开头部分相同的文件,比如
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
字节筑梦
字节筑梦
Lv1
时间像奔腾澎湃的急湍,它一去无还,毫不留恋。
文章
3
粉丝
0
获赞
0