Skip to content

πŸƒπŸƒ 52 SVG Playingcards in a 14 KB Custom Element πŸƒπŸƒ

Notifications You must be signed in to change notification settings

cardmeister/cardmeister.github.io

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

52 SVG playingcards in one 14 KBΒ² Custom Element: <playing-card>

No FrameworksΒ Β  No DependenciesΒ Β  No External SVG files!Β Β  All SVG is created by the Custom Element

This project uses modern browser technologies. Open in a 'evergreen' browser
If you want to make it work in outdated browsers see the WebComponents polyfill

License: Unlicense: This is free software released into the public domain - https://choosealicense.com/licenses/unlicense/


The single file elements.cardmeister.min.js is:

  • SVG data for 52 playing cards - 500 KB painstakingly slimmed

  • one W3C Autonomous Custom Element : <playing-card cid=Queen-of-Hearts> </playing-card>

  • 52 customized Built-In IMG elements : <img is=queen-of-hearts> (not supported in Safari; no longer created since 2024)

  • Total GZip file size: (under) 14 KBΒ² creating 52 playingcards:


Why another SVG playing card

A 'Hello World!' with Framework X took hours installing tools and used 95 KiloBytes ... to display 12 characters.

Something did not feel right!
What happened to the days when all you needed were some HTML tags and a text-editor?

I learned to PEEK and POKE at the age of 10 on a TRS-80 and learned HTML (a bit late) at 25
The ability to 'peek at' and learn from someone else's effort was fantastic.

Alas in the past years 'Web Development' has become something For-Rocket-Scientists only

W3C standard Custom ElementsΒΉ make writing semantic HTML as cool as it was in my early days .. without any Framework!

Playingcard(t)s are a good subject to demonstrate the power of a Custom Element

  • one single file creates a <playing-card> Custom Element
  • (loads of) attributes for configuration
  • 52 SVG playingcards
  • no external SVG images
  • All in less than 16 KBΒ² because my first computer had 16 KiloBytes memory

Feel free to PEEK around, and if you want to POKE, submit an issue.

A special thanks to users Supersharp and Intervalia for their always helpful answers on StackOverflow!

-- Danny Engelman
-- Amsterdam 🌷🌷🌷 the Netherlands
-- Summer 2019 πŸ‘΄πŸ½ 25 years after my first HTML page

  1. The terms Custom Elements & Web Components are used interchangeably.
    Web Components is the umbrella term for the three main techonologies:

  2. Minified 16 KB GZip
    It is a card game .. I cheated ... but can you spot where? (explained in documentation below)


How the <playing-card> Custom-Element works

For an introduction to W3C standard Custom Elements/WebComponents read the excellent Blog by Danny Moerkerke

Like the HTML5 <video> tag, the Custom Element <playing-card> abstracts complex functionality into one HTML tag

πŸ’‘ Custom Elements must be declared in a Namespace, so require a - hyphen in the tag name

Minimal HTML file to display a Queen of Hearts:

<html>
  <head>
    <script src="elements.cardmeister.full.js"></script>
  </head>
  <body>
    <playing-card rank="Queen" suit="Hearts"></playing-card>
  </body>
</html>

(Autonomous) Custom Element <playing-card> creates an image with the SVG as data:image src

Saves you from headaches with SVG in a document (duplicate symbol ids, bleeding CSS etc.)
and makes it easy to add HTML Drag-Drop functionality.

πŸ’‘ Autonomous Custom Elements require a closing tag: </playing-card>

How it looks in F12 Developer Tools:

Customized Built-In Element (from IMG)

52 Custom Elements are also created customizing the IMG element so you can use:

<img is="ten-of-hearts" />
<img is="jack-of-hearts" />
<img is="queen-of-hearts" />
<img is="king-of-hearts" />
<img is="ace-of-hearts" />

The Autonomous Custom Element <playing-card> and the Customized Built-In Element <img is=...>
are two different flavours of Custom Elements which (in this case) do the same.
You can pick the one that suits your application. Or use them both in one page: https://cardmeister.github.io

πŸ’‘ requires a polyfill in Safari, because Apple does not want to implement this part of the spec.

πŸ’‘ declaration must be all lowercase!

πŸ’‘ the IMG element is self-closing by default, no closing tag required!

πŸ’‘ the is= declaration only takes effect when the DOM element is created

πŸ’‘ on the (customized IMG) element you can use all attributes/properties documented below

πŸ‘¨β€πŸ’» tech: Autonomous Elements can have ShadowDom, Customized Elements can not have ShadowDom

πŸ‘¨β€πŸ’» tech: Because IMG elements don't contain text or have descendants, neither CSS selector img:before or img:after can be used

Styling <playing-card> with CSS:

Since <playing-card> only creates a single IMG no shadow-DOM is required/used, thus IMG can be styled with global CSS:

[rank="queen"] img {
  border: 3px solid greenyellow;
  transform: rotate(15deg);
}

or for the customized IMG element:

img[is*="queen"] {
  border: 3px solid greenyellow;
  transform: rotate(15deg);
}


My next challenge: a Solitaire game in Custom Elements

I have the selection and drag/drop part nearly done: https://playing-cards.github.io/Solitaire/

<cardts-game>
  <cardts-deck type="frenchdeck"></cardts-deck>
  <cardts-pile>
    <% 7 times %>
    <cardts-pile type="sequence"></cardts-pile>
    <%%>
  </cardts-pile>
  <cardts-pile id="foundation" type="stack">
    <% 4 times %>
    <cardts-pile></cardts-pile>
    <%%>
  </cardts-pile>
  <cardts-game></cardts-game
></cardts-game>

or even weirder <playing-card> games ...

What if cardts could:

  • change suit during a game
  • fade out during a game
  • change from Queens to Kings
  • ...
  • ...




<playing-card> attribute/properties syntax

playingcards Terminology:

  • 'court' is UK English for 'face' US English. So Jack, Queen and King are court-cards (courts)
  • SHDC is short for Spades-Hearts-Diamonds-Clubs
  • 0123 are Array indexes (SHDC)
  • cid = card id
    As = Ace of Spades , TD = Ten of Diamonds

πŸ’‘ attributes can be written as attributes in HTML:<playing-card id=MyCard cid=Qh ></playing-card>

πŸ’‘ attributes can be set as attribute:MyCard.setAttribute('cid','Kh')

πŸ’‘ or as property: MyCard.cid='Kh' (sets the attribute value)

<playing-card> takes a sh*tload of attributes you can play with:

See: https://cardmeister.github.io

  • cid - Standard Card ID notation: cid=Qh = Queen of Hearts
  • letters - Custom localized court letters
  • courts - mix rank/courts images
  • suits - Mix suit/court images
  • suitcolor - Change SHDC suit color
  • rankcolor - Change rank color
  • cardcolor - card color
  • opacity - set card content opacity
  • courtcolors - Change court image colors
  • bordercolor - set card border color
  • borderradius - set card border radius
  • borderline - set card border line thickness
  • backtext - card backside text
  • backtextcolor - card backside color
  • svg - (undocumented) add attributes to main SVG definition (eg: svg="transform='rotate(5,5,5)'")
  • pips - (undocumented) custom suitsymbols (pips) on number cards

πŸ”§ cid - standard card id notation

    <playing-card cid=As></playing-card>
    <playing-card cid=5d></playing-card>
    <playing-card cid=Tc></playing-card>
    <playing-card cid=Jh></playing-card>
    <playing-card cid=Qc></playing-card>

πŸ’‘ overrules rank= and suit= notation

πŸ’‘ case insensitive: qC is the same as: Qc

πŸ’‘ 10C is processed as TC

πŸ’‘ Queen-of-Clubs is processed as Qc

πŸ’‘ <img is=queen-of-clubs> uses cid internally - is= requires lowercase full Element name!!

πŸ”§ letters - Custom localized court letters

default: AJQK

Rename Ace, Jack, Queen, King letters AJQK to Dutch locale 'Aas Boer Vrouw Heer'

    <playing-card suit=Spades   rank=Ace   letters=ABVH></playing-card>
    <playing-card suit=Hearts   rank=Jack  letters=ABVH></playing-card>
    <playing-card suit=Diamonds rank=Queen letters=ABVH></playing-card>
    <playing-card suit=Clubs    rank=King  letters=ABVH></playing-card>

πŸ’‘ a 4 letter string is used so it can be a global setting for all created cards

πŸ’‘ Custom letters draw all ranks (A,2,3,4 etc.) in Arial font

πŸ’‘ letters can not be Emoji (Unicode strings)

πŸ”§ courts - mix rank/courts images

default: 012

Before anyone complains the Queen is always #2

Rearrange SHDC court images to KJQ = 201 :

<playing-card suit="Hearts" rank="Jack" courts="201"></playing-card>
<playing-card suit="Spades" rank="Queen" courts="201"></playing-card>
<playing-card suit="Diamonds" rank="King" courts="201"></playing-card>

πŸ”§ backcolor - card backside color

default: #E55 (red)

<playing-card rank="0" backcolor="red"></playing-card>
<playing-card cid="00"></playing-card>
<playing-card
  rank="0"
  backcolor="#44F"
  backtext="COPYRIGHT"
  backtextcolor="black"
></playing-card>

πŸ”§ suitcolor - Change SHDC suit color

πŸ”§ rankcolor - Change rank color

default: #000,red,red,#000

(rankcolor added on request in 2024; can also be a single color value)

πŸ”§ cardcolor - card color

default: #FFF

<playing-card
  rank="1"
  suit="0"
  suitcolor="#FFF,#FFF,#FFF,red"
  cardcolor="red"
></playing-card>
<playing-card
  rank="1"
  suit="1"
  suitcolor="#FFF,yellow,#FFF,red"
  cardcolor="green"
></playing-card>
<playing-card
  rank="1"
  suit="2"
  suitcolor="#FFF,#FFF,black,red"
  cardcolor="dodgerblue"
></playing-card>
<playing-card
  rank="1"
  suit="3"
  suitcolor="#FFF,#FFF,#FFF,red"
  cardcolor="yellow"
></playing-card>

The string (all 4 settings in one string for global declaration!) is converted to an array so can be written as:

<playing-card rank="1" suit="0" suitcolor="#FFF," cardcolor="red"></playing-card>
<playing-card rank="1" suit="1" suitcolor=",yellow" cardcolor="green"></playing-card>
<playing-card rank="1" suit="2" suitcolor=",,black" cardcolor="dodgerblue"></playing-card>
<playing-card rank="1" suit="3" suitcolor=",,,red" cardcolor="yellow"></playing-card>

πŸ’‘ You can change one card with: element.suitcolor='blue';

πŸ’‘ non-color values will break the SVG suit display

πŸ’‘ add attribute norank=true and only the big center suit remains

πŸ”§ opacity - set card content opacity

default: 0.8

πŸ’‘ 0.8 makes the cards look not too sharp, and you can highlight playing-cards by setting opacity to 1

<playing-card rank="1" suit="0" opacity="1"></playing-card>
<playing-card rank="1" suit="1" opacity=".75"></playing-card>
<playing-card rank="1" suit="2" opacity=".5"></playing-card>
<playing-card rank="1" suit="3" opacity=".1"></playing-card>

πŸ’‘ CSS opacity makes the whole IMG transparent! <playing-card opacity=n> leaves the <playing-card> backcolor at 100%

πŸ”§ bordercolor - set card border color

default: #444 (darkgray)

πŸ”§ borderradius - set card border radius

default: 12

πŸ”§ borderline - set card border line thickness

default: 1

<playing-card
  rank="1"
  suit="0"
  bordercolor="red"
  borderradius="12"
  borderline="12"
></playing-card>
<playing-card
  rank="4"
  suit="0"
  bordercolor="hotpink"
  borderradius="100%"
  borderline="20"
></playing-card>
<playing-card
  rank="13"
  suit="H"
  bordercolor="gold"
  borderradius="12"
  borderline="38"
></playing-card>

☠️ courtcolors - Change court image colors

this setting will most likely be gone in 'less SVG' version 2

default: #DB3,red,#44F,#000,#000,4 (gold,red,blue,black,blacklines,linethickness)

<playing-card
  suit="Hearts"
  rank="Jack"
  courtcolors="gold,red,blue,black,black,4"
></playing-card>
<playing-card
  suit="Spades"
  rank="Queen"
  courtcolors="#DB3,lightcoral,lightblue,slategray,#000,1"
></playing-card>
<playing-card
  suit="Diamonds"
  rank="King"
  courtcolors="gold,red,green,orange,#000,4"
></playing-card>

πŸ’‘ suit decorations are set by the suitcolor attribute


SVG cards are huge. How I reduced 500 KB SVG

All good open-source SVG playingcards available are high-precision ready for print.

In the HTML5 deck of cards the single King of Hearts 1_13.svg card is 69 KB

<playing-card> creates all 52 cards in 16 KB

  • I started with the 550 KB for 52 CC-0 licensed cards from the card generator by Adrian Kennard
  • reduced precision with: Jake Archibalds GUI for SVGO
    (this causes some alignment 'errors' you only see viewing a court card full screen)
  • spent some time in Inkscape reducing details you don't see on a computer screen
  • cut everything up in separate SVG paths
  • made redundant paths into single JS functions/variables
  • (see below) cleaned up the Jack of Spades court which (since ages?) had the suit on the wrong? side
  • wrote a SVGcardt() function to create an IMG with SVG data
  • Re-drawing the Heart pips on the Jack, I saw an opportunity to cheat and save 70% of SVG data (see FAQ)

A note on data compression

The SVG can be reduced more using a LZMA compressor
But HTTP requires Base64 encoding.
And the Browser needs to decompress the data which takes a hefty 200ms (and a 6.9KB decoder)

The first PoC did LZMA de-compression and lazy loaded all 12 court images.

Since GZip is a similar LZ compression technique (over the whole file) there is only a gain on slow 3G connections


Frequently Asked Questions

πŸƒ Why <playing-card>

A Custom Element requires its own Namespace, so you are stuck to something with a - hyphen.

You can create 52 Autonomous Custom Elements (extend from <playing-card>):

<queen-of-hearts></queen-of-hearts>
<ten-of-spades></ten-of-spades>
...

But there is little value as you can not use partial Selectors for tag names.

To select all Spades you still need attributes:

<queen-of-hearts hearts></queen-of-hearts>
<ten-of-spades spades></ten-of-spades>
...

Because queen-of-hearts can either be a 'Autonomous Custom Element' OR a 'Customized Built-In Element'
<playing-card> creates 52 Customized IMG elements:

<img is="ten-of-hearts" />
<img is="jack-of-hearts" />
<img is="queen-of-hearts" />
<img is="king-of-hearts" />
<img is="ace-of-hearts" />

πŸ’‘ declaration must be all lowercase!

πŸ’‘ the IMG element is self-closing by default, no closing tag required!

πŸ’‘ the is= declaration only takes effect when the DOM element is created

πŸ’‘ on the (customized IMG) element you can use all attributes/properties documented above

πŸƒ How about separating Element and SVG data?

Yes, an option is to exclude the (compressed/raw) SVG data from the Element file.
The Element can then fetch the data asynchronous.

This project was about stuffing everything into one file.

πŸƒ Where is the NPM installer?

If you need an installer to copy one file: elements.cardmeister.min.js

You might find a better career flipping burgers at McDonalds.

Read: I stuck the UNlicense on this code. You can do whatever you want with the code.
I don't want to be responsible for maintaining a dependency for others

πŸƒ Where is the SVGcardt() source?

You do NOT need the SVGcardt() Source Code to use <playing-card> or <img is=..> in applications

The <playing-card> Custom Element declaration in elements.cardmeister.min.js is unlicensed.
You can rename <playing-card> to anything you want and customize the Custom Element to your liking.

The <img is=..> declaration is part of the minified SVGcardt() code.

The SVG creation code took some blood, sweat and tears and is closed source for now.
It needs some refactoring and documentation before this is public ready.
Watch this GitHub repo and you will know when it becomes available.

πŸƒ Are cardts really created in the browser/not downloaded?

See F12 Network tab, data:image/svg cardts take 0 milliseconds download time

Note: FireFox is noticeably slower in drawing the cards.

Creating the card does take CPU time. Maybe a WebWorker can improve performance (but I can't stick JScode and a WebWorker in one single file)

πŸƒ Where is the Joker?

πŸ˜‰ Causing havoc in Gotham City?!

I haven't found a good Joker Card design yet, if you have one let me know

πŸƒ Why not [..fill in WC Library/Framework name..] ?

I wanted to learn how far I could go with vanilla JavaScript
Then I wanted to proof you don't need libraries/frameworks


πŸƒ Where/How did you cheat to get all SVG in 16KB element?

Look closely at the court cards....

The 16 KB version uses the same (JQK Hearts) court image with slightly different colors to distract your eye.

πŸ”§ suits - Mix suit/court images

default: 0123 Β  (defaults to 1111 in the 16 KB version!)

Change Spades=0, Hearts=1, Diamonds=2, Clubs=3 court image:

    <playing-card suit=S rank=Queen suits=1111></playing-card>
    <playing-card suit=H rank=Queen suits=1111></playing-card>
    <playing-card suit=D rank=Queen suits=1111></playing-card>
    <playing-card suit=C rank=Queen suits=1111></playing-card>

Cheating Queens:

The full <playing-card> version with 12 unique SVG courts:

The Full Version elements.cardmeister.full.js with 12 different court images adds 110 KB raw SVG data

Making the single file 60 KB GZipped!

See: index.html?#full - πŸ“„ source

Difference between the .min. and the .full. version

Apart from the court images there are no differences.

It was fun (and took some time) breaking that 16 KB barrier, and helped making the full version smaller as well.

Use the Full version !

The Min version can be used for slow/low-bandwidth applications.


Challenge - built a game using <playing-card>

Create FreeCell with HTML Custom Elements

inspiration: https://www.free-freecell-solitaire.com/freecell.html

(something like) This HTML should create ALL game functionality

<cardts-game>
  <cardts-pile id="freecells" type="any">
    <% 4 times %>
    <cardts-pile max="1"></cardts-pile>
    <%%>
  </cardts-pile>
  <cardts-pile id="foundation" type="stack">
    <% 4 times %>
    <cardts-pile></cardts-pile>
    <%%>
  </cardts-pile>
  <cardts-pile>
    <% 8 times %>
    <cardts-pile type="sequence"></cardts-pile>
    <%%>
  </cardts-pile>
  <cardts-deck type="frenchdeck"></cardts-deck>
  <cardts-game></cardts-game
></cardts-game>

Here is some help to create the foundation for the SHDC foundation piles:

<playing-card cid="F0"></playing-card>
<playing-card cid="FH"></playing-card>
<playing-card cid="F2"></playing-card>
<playing-card cid="FC"></playing-card>


License: unlicense

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to http://unlicense.org


You've got to know when to fold them...

https://www.youtube.com/watch?v=7hx4gdlfamo&t=1s

or the Muppets version: https://www.youtube.com/watch?v=kNnrTNFWcsg


(some) Used Resources

Custom Elements/Web Components

Cards

SVG

LZMA compression

Chess

Yes, I did https://chessmeister.github.io

<img is=white-queen at=D5>

Code Fragments

Load SVG content in main document:

  <iframe src="file.svg" ></iframe>


Published: 2020-08-06 17:13

About

πŸƒπŸƒ 52 SVG Playingcards in a 14 KB Custom Element πŸƒπŸƒ

Topics

Resources

Stars

Watchers

Forks

Languages