12

I'm trying to develop a typing speed competition using JavaScript. People should write all the words they see from a div to a textarea.

To prevent cheating (like copying the words from div) one way is check the written words only when a keyboard key is down, but I was wondering if there is a way to prevent the user from copying the text in a browser?

What I have tried so far:

  1. Disable right click (didn't work on mobile browsers)
  2. Show an alert using the onmousedown event in all the page (it didn't work either)

Using any libraries is OK.

Jafar Akhondali
  • 1,552
  • 1
  • 11
  • 25
  • 7
    Don't you mean you want to prevent _pasting_? – Mr Lister Sep 26 '15 at 10:41
  • 2
    Detecting paste is pretty simple: http://jsfiddle.net/fpkdkpvm/ – rybo111 Sep 26 '15 at 10:59
  • 2
    For the sake of not screwing with standard expected behaviour, I encourage you to prevent pasting the words rather than copying them. Preventing copying isn't AS bad as preventing context menus, but it's still frustrating to encounter. – Soron Sep 26 '15 at 13:43
  • Personally I wouldn't bother worrying about the user copying the text. Just worry about them being able to paste it. – rybo111 Sep 26 '15 at 14:46
  • 1
    Remember that you can use features such as Inspect Element in Chrome, which would allow the user to copy the text, or delete elements that stop them from copying text. – rybo111 Sep 26 '15 at 15:34
  • 1
    Remember that you can easily change input methods on mobile. Switching to voice recognition might give an advantage. Not sure if that can be detected as it's basically just another input device. – null Sep 26 '15 at 17:44
  • Pls also see this related question http://stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-highlighting – Amitd Sep 26 '15 at 19:33
  • @null has a very good point. It is the input you want to be concerned about, not the text you're displaying. My answer literally monitors letter-by-letter changes. – rybo111 Sep 27 '15 at 07:54
  • 1
    @null Google keyboard even suggests the next word as you type. – rybo111 Sep 27 '15 at 08:00
  • @MrLister Oh, noes! Certainly not pasting. I have a Punto Switcher: that's a program to help you to transliterate the garbage you've written when the current input language was wrong. On hotkey press it cuts the garbage and pastes the decoded text in another input language. It's unfair to forbid this during typing speed competitions. – polkovnikov.ph Oct 03 '15 at 19:41
  • @Black-Hole Whatever coarse cheating heuristics you'll manage to use in your software, also use more accurate metrics like timing between key presses. You can compare distribution of such timings among the normal user and a cheater, and the cheater will be visible even if he tries hardly to hide this fact. – polkovnikov.ph Oct 03 '15 at 19:45
  • You realize your hijacking the persons browser. you should never do that. the expected behavior should always work. you just piss people off and come off as a controller, not a libertarian. The name of this addon took the words right out of my mouth: https://chrome.google.com/webstore/detail/dont-fuck-with-paste/nkgllhigpcljnhoakjkgaieabnkmgdkb/related?hl=en – Northstrider Apr 08 '17 at 11:54

11 Answers11

13

You can simply make the text into an image.

<style type="text/css">
div.image {
    width: 100px;
    height: 100px;
    background-image: url-to-your-image;
}
</style>

To generate the images you can use a server side script as in the aswers of this question

or something like this:

<?php
header("Content-type: image/png");
$im = @imagecreate(210, 30)
or die("Cannot Initialize new GD image stream");
$background_color = imagecolorallocate($im, 255, 255, 255);
$text_color = imagecolorallocate($im, 0, 0, 0);
imagestring($im, 4, 5, 5,  "This is a test", $text_color);
imagepng($im);
imagedestroy($im);
?> 

Test here

Community
  • 1
  • 1
Max
  • 262
  • 2
  • 8
  • 5
    Perfect solution. Solves the real XY problem. – Hylianpuffball Sep 26 '15 at 16:29
  • server side script like php o asp will do, or you can write a script/program that make the conversion offline and put the images on the server – Max Sep 26 '15 at 17:32
  • This will prevent perfect text wrapping from happening so the server has to be able to deal with all width – WorldSEnder Sep 26 '15 at 17:58
  • I'm not a php guru, but I'm sure this is doable – Max Sep 26 '15 at 18:11
  • @WorldSEnder P.S. the OP only asked a method to prevent copy & paste in the browser... – Max Sep 26 '15 at 18:26
  • 1
    This is the best solution in that it defeats attempts to bypass by modifying javascript locally. – Siyuan Ren Sep 26 '15 at 20:00
  • 2
    Doesn't work if the user is using OCR which should have no problem with typed text – Belgi Sep 26 '15 at 20:22
  • 1
    @Belgi yeah, nothing should work if the user is motivated enough, i.e. the user can hijack the browser rendering engine to put the content of the div in the clipboard :P – Max Sep 26 '15 at 20:53
  • @Belgi: That is a different problem (namely the CAPTCHA problem). – Kevin Sep 27 '15 at 00:47
  • **Why** would you create an image as a `div` with a `background-image` property, specified in the stylesheet no less (not in a `style` attribute)? – user253751 Sep 27 '15 at 07:45
  • @Hylianpuffball a perfect solution would stop copy-paste, voice typing, word auto-suggestions, etc. – rybo111 Sep 27 '15 at 09:19
  • 1
    @rybo111 a perfect solution is to brick the users phone! LOL – Max Sep 27 '15 at 09:22
  • It does work but please be aware that you are breaking many accessibility tools. Never forget our disabled fellow citizens. – tobiak777 Sep 27 '15 at 09:54
6

You can prevent the user from actually selecting the text so it can not be copied - however I'd still combine this with paste detection as others recommended

.noselect {
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
<p>this can be selected</p>
<p class="noselect">this can NOT be selected</p>

But the user can still open the page source and copy it from there.

gyantasaurus
  • 447
  • 2
  • 9
  • 2
    'user-select' isn't [standard](https://developer.mozilla.org/en-US/docs/Web/CSS/user-select), may not be ideal to use as said on Mozilla MDN: **Non-standard** This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future. – cateyes Sep 26 '15 at 21:33
  • 1
    @cateyes, good info. Along the same lines (*prevent the user from actually selecting the text*), one can apply `pointer-events: none` to the `div`, which is standard and widely supported. – Michael Benjamin Sep 28 '15 at 13:58
5

One crazy way of doing this is, laying out another absolutely positioned element on top of this. But this will disallow clicking of links too! May be you can do it with position: relative and a higher z-index.

.content {position: relative;}
.content .mask {position: absolute; z-index: 1; width: 100%; height: 100%;}
.content a {position: relative; z-index: 3;}
<div class="content">
  <div class="mask"></div>
  <p>Pages that you view in incognito tabs won’t stick around in your browser’s history, cookie store or search history after you’ve closed <strong>all</strong> of your incognito tabs. Any files that you download or bookmarks that you create will be kept. <a href="https://support.google.com/chrome/?p=incognito">Learn more about incognito browsing</a></p>
</div>

Try using the touch or longpress events.

<!DOCTYPE html>
<html>
<head>
  <script>
    function absorbEvent_(event) {
      var e = event || window.event;
      e.preventDefault && e.preventDefault();
      e.stopPropagation && e.stopPropagation();
      e.cancelBubble = true;
      e.returnValue = false;
      return false;
    }

    function preventLongPressMenu(node) {
      node.ontouchstart = absorbEvent_;
      node.ontouchmove = absorbEvent_;
      node.ontouchend = absorbEvent_;
      node.ontouchcancel = absorbEvent_;
    }

    function init() {
      preventLongPressMenu(document.getElementById('theimage'));
    }
  </script>
</head>
<body onload="init()">
  <img id="theimage" src="http://www.google.com/logos/arthurboyd2010-hp.jpg" width="400">
</body>
</html>

Source

Community
  • 1
  • 1
Praveen Kumar Purushothaman
  • 164,888
  • 24
  • 203
  • 252
4

Try putting a transparent div over the text. I have used jQuery here. That should work.

var position = $('#textInHere').position();
$('#noClickThroughThis').css({
    height: ($('#textInHere').height()),
    width:  ($('#textInHere').width()),
    position: 'absolute',
    top: position.top,
    left: position.left,
    'z-index': 100
});

Here is a fiddle http://jsfiddle.net/lacrioque/tc4bwejn/

Lacrioque
  • 358
  • 7
  • 11
3

It's easy to disable the paste feature by using jQuery. For example, if you have an edit field like this one:

<p id='someInput' contenteditable='true'>Here is the text</p>

Then, this piece of jQuery code will disable the pasting feature on it:

$('#someInput').on('paste', function(e) {
  return false;
});
cateyes
  • 5,208
  • 2
  • 24
  • 31
3

A good way to work out if a user is cheating is to compare the current input length to the last input length. You can use a data attribute to store the previous value (or length):

<textarea class="typing-only" data-temp=""></textarea>

jQuery:

$(document).on('input', '.typing-only', function(){
    if((this.value.length - 1) > $(this).data('temp').length){
        alert('Cheat!');
    }
    $(this).data('temp', this.value);
});

JSFiddle demo

rybo111
  • 12,240
  • 4
  • 61
  • 70
3

pointer-events: none

CSS pointer-events allows you to control the interaction between an element and the mouse. When set to none, the element is never the target of mouse events.

MDN definition page

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
2

You can try using :after tag and styling it with content: "Text"; in css, AFAIK you cannot select :before and :after's content.

2

Thanks for your amazing solutions. I tested all of them, and in short some of them worked only on a PC, some only on Chrome and Firefox and some only on Safari, but unfortunately none of them worked 100%.

Although @Max answer might be safest, I didn't tag with PHP in the question because if I use this solution dealing with answers, it will be hard because I don't have access to words on the client side!

So the ultimate solution I came with was combining all of the provided answers plus some new methods (like clearing the clipboard every second) into a jQuery plugin. Now it works on multiple elements too and worked 100% on PC browsers, Firefox, Chrome, and Safari.


What this plugin does

  1. Prevent pasting (optional)
  2. Clearing clipboard (looks like it doesn't work well)
  3. Absorbs all touch events
  4. Disable right click
  5. Disable user selections
  6. Disable pointer events
  7. Add a mask with a z-index inside any selected DOM
  8. Add a transparent div on any selected DOM

A jsFiddle:

(function($) {

    $.fn.blockCopy = function(options) {

        var settings = $.extend({
            blockPasteClass    : null
        }, options);

        if(settings.blockPasteClass){
            $("." + settings.blockPasteClass ).bind('copy paste cut drag drop', function (e) {
                e.preventDefault();
                return false;
            });
        }

        function style_appender(rule){
            $('html > head').append($('<style>'+rule+'</style>'));
        }

        function html_appender(html){
            $("body").append(html);
        }

        function clearClipboard() {
            var $temp = $("#bypasser");
            $temp.val("You can't cheat !").select();
            document.execCommand("copy");
        }

        function add_absolute_div(id) {
            html_appender("<div id='noClick"+id+"' onclick='return false;' oncontextmenu='return false;'>&nbsp;</div>");
        }

        function absorbEvent_(event) {
            var e = event || window.event;
            e.preventDefault && e.preventDefault();
            e.stopPropagation && e.stopPropagation();
            e.cancelBubble = true;
            e.returnValue = false;
            return false;
        }

        function preventLongPressMenu(node) {
            node.ontouchstart = absorbEvent_;
            node.ontouchmove = absorbEvent_;
            node.ontouchend = absorbEvent_;
            node.ontouchcancel = absorbEvent_;
        }

        function set_absolute_div(element,id){
            var position = element.position();
            var noclick = "#noClick" + id;

            $(noclick).css({
                height: (element.height()),
                width:    (element.width()),
                position: 'absolute',
                top: position.top,
                left: position.left,
                'z-index': 100
            })
        }


        $("body").bind("contextmenu", function(e) {
            e.preventDefault();
        });

        //Append needed rules to CSS
        style_appender(
            "* {-moz-user-select: none !important; -khtml-user-select: none !important;   -webkit-user-select: none !important; -ms-user-select: none !important;   user-select: none !important; }"+
            ".content {position: relative !important; }" +
            ".content .mask {position: absolute !important ; z-index: 1 !important; width: 100% !important; height: 100%!important;}" +
            ".content a {position: relative !important; z-index: 3 !important;}"+
            ".content, .content .mask{ pointer-events: none;}"
        );


        //Append an input to clear the clipboard
        html_appender("<input id='bypasser' value='nothing' type='hidden'>");

        //Clearing clipboard Intervali
        setInterval(clearClipboard,1000);

        var id = 1;

        return this.each( function() {

            //Preventing using touch events
            preventLongPressMenu($(this));

            //Add CSS preventer rules to selected DOM & append mask to class
            $(this).addClass("content").append("<div class='mask'></div>");

            //Append an absolute div to body
            add_absolute_div(id);

            //Set position of the div to selected DOM
            set_absolute_div($(this),id);

            id++;
        });
    }
}(jQuery));

Usage

$(document).ready(function(){

    $(".words").blockCopy({
        blockPasteClass : "noPasting"
    });

});

HTML for demo:

<div class="words">Test1: Can you copy me or not?</div><br>
<div class="words">Test2: Can you <br> copy me or not?</div><br>
<textarea class="words">Test3: Can you <br>copy me or not?</textarea><br>


<textarea  class="noPasting"   placeholder="Test1: Paste content if you can"   ></textarea><br>

<textarea  class="noPasting"   placeholder="Test2: Paste content if you can"   ></textarea>

Let me know your opinions. Thanks.

Sources

Community
  • 1
  • 1
Jafar Akhondali
  • 1,552
  • 1
  • 11
  • 25
2

A simpler solution than the accepted one would be to simply use a canvas element with filltext

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.fillText("Can't copy this", 5, 30);
<canvas id="myCanvas"></canvas>

JSFiddle example

Paul Hansen
  • 1,167
  • 1
  • 10
  • 23
0

You can return false on jQuery's cut copy paste events.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
$(document).on("cut copy paste", function(event){
  return false;
});
</script>
<textarea>Try to copy my text</textarea>
Unmitigated
  • 76,500
  • 11
  • 62
  • 80