Search code examples
javascripthtmlcsssvgbrowser

Confuse browsers in-built "find in page" feature


I'm developing a real-time multiplayer web/browser game where players must click on words starting with a specific letter. However, it's prone to cheating as users can exploit the browser's find-in-page feature.

After researching, I discovered that preventing find-in-page is challenging. Despite implementing a solution by appending the letter "r" to each word with opacity and font-size: 0, cheating is still possible.

I'm considering a new approach: creating SVGs for each letter and mapping them to the corresponding letters in the game. This might prevent find-in-page from working effectively. Before investing significant time into this, I'd like to know if it's a viable solution. Additionally, I'm open to alternative suggestions.

Is using SVGs a practical solution to confuse find-in-page, or do you have other recommendations? I appreciate any insights.


Solution

  • Nice "Free Friday" Challenge

    Keeping all logic client side I would:

    • Read the text from <words-puzzle> Web Component innerHTML
    • process each word by <news-word> Web Component
      • reverse the word/letters
      • add decoy <span> to make reading HTML difficult (DOM parsing will take some time..)
      • write letter in a span
      • !! display the letters direction:rtl to confuse HTML readers even more
      • Only add a onclick handler when the First letter matches
    • Overwrite the <words-puzzle> innerHTML with the new "messy" HTML

    <style>
      news-word {
        unicode-bidi: embed;
        direction: rtl;
        cursor: pointer;
      }
      span {
        display: inline-block;
      }
    </style>
    <words-puzzle find="a">
      Web Components is a suite of different technologies allowing you to create reusable 
      custom elements — with their functionality encapsulated away from the rest of your 
      code — and utilize them in your web apps.
    </words-puzzle>
    <script>
      customElements.define("news-word", class extends HTMLElement {
        connectedCallback() {
          let word = this.getAttribute("word");
          let decoy = "<span></span>".repeat(666);
          this.innerHTML = word.split("").reverse().map(letter => {
            return decoy + `<span>${letter}</span>` + decoy;
          }).join("");
          this.removeAttribute("word"); // clean up!!
          let puzzle = this.closest("words-puzzle");
          if (puzzle.find == word[0]) {
            puzzle.h3 = (++puzzle.findcount);
            this.onclick = () => {
               this.style.backgroundColor = "lightgreen";
               puzzle.h3 = (--puzzle.findcount);
            }
          }
        }
      })
      customElements.define("words-puzzle", class extends HTMLElement {
        get find() {
          return this.getAttribute("find")
        }
        set h3(cnt) {
          this.querySelector("h3").innerHTML = `Click the ${cnt} words starting with the letter: ${this.find}`;
        }
        connectedCallback() {
          this.findcount = 0;
          setTimeout(() =>
            this.innerHTML = `<h3></h3>` +
            this.innerHTML.trim().split(" ").map(word => {
              return `<span><news-word word="${word}"></news-word></span>`
            }).join(" "))
        }
      })
    </script>

    Version 2

    Ah, now I understand what you want.

    Wack in transparent letters to make Ctrl-F selection unreadable

    <style>
      news-word {
        unicode-bidi: embed;
        direction: rtl;
        cursor: pointer;
      }
      span {
        display: inline-block;
        position: relative;
      }
      .under {
        position: absolute;
        right:0;
        color:transparent;
      }
    </style>
    <words-puzzle find="a">
      Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.
    </words-puzzle>
    <script>
      customElements.define("news-word", class extends HTMLElement {
        connectedCallback() {
          let word = this.getAttribute("word");
          this.innerHTML = word.split("").reverse().map(letter => {
            return `<span>${letter}<span class="under">a</span></span>`;
          }).join("");
          this.removeAttribute("word"); // clean up!!
          let puzzle = this.closest("words-puzzle");
          if (puzzle.find == word[0]) {
            puzzle.h3 = (puzzle.findcount++);
            this.onclick = () => this.style.backgroundColor = "lightgreen";
          }
        }
      })
      customElements.define("words-puzzle", class extends HTMLElement {
        get find() {
          return this.getAttribute("find")
        }
        set h3(cnt) {
          this.querySelector("h3").innerHTML = `Click the ${cnt} words starting with the letter: ${this.find}`;
        }
        connectedCallback() {
          this.findcount = 0;
          setTimeout(() =>
            this.innerHTML = `<h3></h3>` +
            this.innerHTML.trim().split(" ").map(word => {
              return `<span><news-word word="${word}"></news-word></span>`
            }).join(" "))
        }
      })
    </script>