chanind / hanzi-writer Goto Github PK
View Code? Open in Web Editor NEWChinese character stroke order animations and practice quizzes
Home Page: https://hanziwriter.org
License: MIT License
Chinese character stroke order animations and practice quizzes
Home Page: https://hanziwriter.org
License: MIT License
Hi,
This library looks great and thanks for writing and sharing it !
I am aiming at generating some practice sheets that display stroke order on the left side.
While looking at the documentation, I could not find a straightforward way to render a fixed image of the character with only one stroke first, then with the first 2 strokes, then the first 3 strokes …
Is that something that could be done easily with this library? And if so, is it also possible to have the last stroke displayed as a different colour ?
Thanks and regards,
Fabien
The current constructor of HanziWriter is calling setCharacter()
. Which is too soon in my code, and don't let me really manage when setCharacter()
gets called. This is a minor issue, but is a little annoying and goes against some (debatable) design principles : a constructor should not do any real work (only setting up inner variables).
I'm willing to make a PR on that, taking into account the fact that you need to keep backwards compatibility.
To elaborate why it is better to avoid any complex behavior in constructors, here are a few discussion about that:
http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/
https://softwareengineering.stackexchange.com/questions/224063
https://softwareengineering.stackexchange.com/questions/159193
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c41-a-constructor-should-create-a-fully-initialized-object
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-factory
As a fix, I would suggest to make it optional (default would still be to call setCharacter()
, but this should be removed in next major release), and provide a factory that would create the widget and load a character HanziWriter.Create()
to still offer the use case of setting up and loading a character in one go.
I'm totally aware that it's kind'a matter of taste here, and I will very happily and kindly understand any dismissal of this proposal.
Hi, thanks for the great library! Small issue -- small strokes take up the whole screen. 丶for instance. Can't think of a simple fix and wondering if you have any suggestions.
Hello there. Thanks a lot for building this amazing library!
I'm trying to use hanzi-writter on an app hosted on heroku.
Everything works well in development but in production I get the following error:
Mixed Content: The page at [...] was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://chanind.github.io/hanzi-writer/cdn/2/data/%E6%B3%95.json'. This request has been blocked; the content must be served over HTTPS.
Is there any way to change the defaultLoader to 'https'?
(I'm not sure this is a good idea, I'm rather new to development).
Alternatively, is there any way to pass the JSON data directly to new HanziWriter instead of passing a custom closure to the charDataLoader option? (the data would already be fetched in advance and there is no need to do a new network call).
Thanks!
Hi there, we from the Wikimedia Commons Stroke Order project are discovering your Hanzi-Writer and MakemeaHanzi projects. These 2 projects are awesome, more powerful than our graphic-designers-centered approach, and I'am so impressed by your both project. Thanks a lot for sharing them under MIT License, it's a blessing for the global community and cross-cultural understanding 👍 💯
Thanks!
For the character animation, is it possible to show the character stroke by stoke?
For example, when one stroke is done, the animation will be paused, the user needs to click a button to continue to the next stroke.
The default loading process allows to use either a promise function or a direct function for the implementation of option charDataLoader
function (and as I understand it):
HanziWriter.prototype._loadCharacterData = function(char) {
if (this.isLoadingCharData) this.cancelLoadingCharData();
this.isLoadingCharData = true;
return new Promise((resolve, reject) => {
this.cancelLoadingCharData = reject;
const returnedData = this._options.charDataLoader(char, resolve);
if (returnedData) resolve(returnedData);
}).then((data) => {
this.isLoadingCharData = false;
return data;
});
};
why not directly force only one usage (the use of promises) for the sake of clarity and avoid having to provide resolve/onSuccess and reject/onFailure (that last one being not implemented for now) as arguments.
Most of the time, if you choose not to load ALL the ~100Mb of the database in RAM, a loader will be forced to wait for disk I/O and as such seem to be a normal candidate for promises, and even if it is not the case, we could have animation or other lightweight threads underway that would be probably happy to have the opportunity to do some processing at that point without hurting the loading process.
To be clearer, I would suggest to call loadCharacter directly as:
this._options.charDataLoader(char).then(...onSuccessCallable..., ...onFailureCallable...)
And remove the intermediate function _loadCharacterData
.
I'm willing to provide you a PR with the modification that this implies, but I want to make sure that makes sense for you before heading forward.
Many thanks for you library !
Could you tag your releases here on GitHub so the changelog and the commits become visible in the release section?
There are many different place where the cancelation of animation will trigger javascript exceptions.
For instance, if asking for a forever animated loop for a known caracter then loading an unknown character will trigger issues in animateForever()
on line var animatePromise = this._characterRenderer.animate(animation);
as this._characterRenderer
was removed in setCharacter()
.
Note: PR #44 tackles a lot of these issues.
The current setting is too easy, drawing outside the stroke is still too easy to be correct.
Could be good idea to add a variable for precision.
drawingPrecision
: number, default 1. The ratio of width of the "shadow stroke" to draw in in order to be correct, 0.5 is "all pen's stroke should be within the character's strokes".Something like that.
This is probably a stupid question, but how can I display the animation at the center of the screen?
I tried things like:
<center>
//Animate
</center>
The only way I found to change the horizontal location was to changed the width and not the height.
var writer = new HanziWriter('character-target-div', '我', {
width: 700,
height: 150,
In strokeMatches.js
, there's this logic starting on line 113
// if there is a better match among strokes the user hasn't drawn yet, the user probably drew the wrong stroke
const laterStrokes = character.strokes.slice(strokeNum + 1);
for (let i = 0; i < laterStrokes.length; i++) {
const laterMatchData = getMatchData(points, laterStrokes[i], options);
if (laterMatchData.isMatch && laterMatchData.avgDist < strokeMatchData.avgDist) return false;
}
This makes perfect sense, but if I have a quiz with higher lenience, it makes it uncharacteristically strict for some characters which have two similar strokes in close proximity to each other, like 月 and 是.
Let's say you have two short horizontal strokes right next to each other: if you draw the first one slightly too far down, it will think you drew the "wrong" stroke because it was closer to the second short horizontal stroke.
Can we have a way to disable this "future stroke" detection? Or perhaps there's a more elegant way to handle this use case.
Awesome tool, but I can't for the life of me get a basic page set up to work with it. While I'm by no means an expert, I have basic webdev knowledge and skillset that should be sufficient to get something like this working. Yet even after reading through the source for the demo pages I haven't made one iota of progress. I'm sure there's just some minor step I'm missing. A simple step-by-step on how get get a single page with a canvas and a character animation and/or quiz would be a huge help.
For example, it's common to write 子 as only two strokes instead of three strokes, and to write the 女 component in 好 with only two strokes. Is it possible to make the quiz recognize these shortcuts?
When hiding/showing a character, it seems that hanzi-writer in actually modifying the opacity of each strokes which draws to concerns:
And as mentionned in a comment in #47 , should we think about CSS animations ?
Currently the website and docs are all in English.
If you'd like to help with this, you can check out the site in the gh-pages
branch. You can run the site locally with jekyll serve
. (you'll need to run bundle install
first)
Simply for awareness. Based on my app and my custom made data collection.
Single touch/click fire a mistakesOnStroke
, returns the flash hint of the correct stroke to draw next. Behavior as expected, but doesn't seems to be a real writing attempt. Maybe the user :
Asking myself as a app dev : should I filter this out ? Should it be counted as a mistakesOnStroke? 🤔
Thanks so much for updating the documentation. With the new docs I was able to get everything working, and even get it embedded inside Anki. Great excitement.
I have access to high quality SVG data for rare characters and variants through Wenlin, and was curious if there was any way I could use SVGs exported from there. Any idea how I could take an SVG and use its stroke data to create an object suitable to use with Hanzi-Writer? I'm guessing half this question belongs on Make me a hanzi, but any help you can offer would be appreciated. I'm working on using Hanzi-Writer inside Anki and am most of the way there. I'll be releasing my work once I clean it up and get the full functionality I'm after. One big one is this need. I'm hoping to eventually be able to set things up so people can export SVGs from Wenlin or retrieve them from other sources, and be able to set up a HW quiz for them in Anki easily. Wenlin even has some unwieldy scripting, so my ideal end goal is I can set things up so people could simply run a couple scripts and generate HW flashcards from it provided they have the rights.
I've had a look at the data format and it seems to be basically normal SVG stroke data just very slightly different. I also don't understand what the "medians" property is all about, and can't seem to find any clear information on this.
Any help appreciated.
The final flashing color should definitively be separated from the hint as visually they represent 2 conflictual visual communication:
Does this make sense ? I can work on that in a few days if this seems okay for you.
For scientific purpose at least, it would be interesting to return the path of the user correct and mistake drawing. Beyond the current binary onCorrectStroke
or onMistake
, paths would provide insight on what the learners draw, why do they fails.
writer.quiz(options) :
onCorrectStroke
: function(data). Called when the user draws a stroke correctly. The function is called with an object containing totalMistakes
which is the total mistakes made during the quiz so far, strokeNum
the current stroke number, mistakesOnStroke
the number of mistakes the user made drawing this stroke, and strokesRemaining
the number of strokes left until the quiz is complete.onMistake
: function(data). Called when the user makes a mistake drawing a stroke. The function is called with an object containing totalMistakes
which is the total mistakes made during the quiz so far, strokeNum
the current stroke number, mistakesOnStroke
the number of mistakes the user made drawing this stroke so far, and strokesRemaining
the number of strokes left until the quiz is complete.On onCorrectStroke
and onMistake
events, the function(data)
is called when the user draws a stroke correctly or incorectly. In data
, add a key-value drawingPath
: the path' s data for the last drawing made by the user.
I would like to use these data to later on plot student's nth stroke's (strokeNum
) drawing path, correct or mistakes, via d3js. The end rendering being something such :
{ character: '馬', strokeNum: 0,
strokeNumColor: 'lightblue', drawingWidth: '1', drawingColor: '#B10000' }
In language learning, it's the field of error analysis.
From what i remember of skitter, it allowed you to miss the origin of the stroke by a much larger amount as long as you got the direction correct.
It looks like https://github.com/chanind/hanzi-writer/blob/master/src/StrokeMatcher.js#L11 is just measuring the distance from the absolutely positioned points?
There is a particular stroke that my users have complained about being too difficult to write. It is the acute angle found in characters like 么 去 公 能 台 and so on.
Here is a screenshot of a rejected stroke from the demo page:
The line is almost entirely inside the outline, so it seems excessively picky to reject it while some other strokes are very lenient, such as the right component of 都. Here is a screenshot of a stroke that was not rejected:
Hanzi-writer looks awesome :) I'll probably use for a Japanese app I'm building https://www.kickstarter.com/projects/265123070/jlpt-gold
I've tested the writing quiz on android and ios and it doesn't seem to capture the touch and move correctly. I've reproduced it here https://youtu.be/i5e9pP_xaeo
I see HanziWriter is listening to touch events but I'm not sure why it isn't working. After the kickstarter campaign I should have more time to look into it.
Thanks!
I'm testing hanzi-writer on mobile platform, and with large displays I have real issue with general performance of animations (probably below 10fps) and it is really ugly (I'm on a google galaxy note 4 with a Android 4.4). Inkstone is based on the same data and same langage and technique here (javascript in webview and tween, the only difference is that they use Cordova, but to my limited knowledge, it shouldn't have any impact) and achieve much better on the same terminals.
I'm still quite new to all this, and I suspect that Inkstone is actually upscaling the resolution rather than drawing more pixel precise animation. I'll post updates if I have time about this topic here for any person finding Hanzi-Writer a little slow and possible technical solutions. Any thoughts on that subject are welcome.
One question I had related to that is : are the CSS animation faster than the javascript ones ? (I'd guess so), and if so, it seems that some actions can be transfered to CSS animation (using transition
and keyframes ...).
Hi there,
Just to let you know, a service will be published tomorrow thanks to your library.
I downloaded your last min.js from your website, but drawingWidth works. As do rads.
Prod : https://hanzi.cri-paris.org/#L2%E5%9B%BD
Credit : see footer 👍 Thanks !
Public : ~8000 Chinese learners in Francophonia :D
Project: #MOOCdeChinois #free .
Publication: 2018/02/05
Licence : MOOCdeChinois is CC-by-sa-nc . I push for OpenSource. My code is MIT.
I hope to redirect to you a hand of good willed JS devs 🥇
Maybe I should redirect them more toward MakeMeHanzi / CJKanimate where more work is needed ?
Can hanzi-writer be used as a standalone desktop-based software on Linux-based operating systems? If not, would you consider modifying it so that it can be used on the desktop? I wouldn't know how to use it otherwise.
The browser used by the Anki flashcard application (windows desktop edition) does not track the real mouse position when quizzing a character. As suggested in https://stackoverflow.com/questions/20957627/how-do-you-transform-event-coordinates-to-svg-coordinates-despite-bogus-getbound, the following works fine:
function HanziWriter(element, character) {
...
this._pt = this._canvas.svg.createSVGPoint();
}
HanziWriter.prototype._getMousePoint = function (evt) {
this._pt.x = evt.clientX;
this._pt.y = evt.clientY;
var _local = this._pt.matrixTransform(this._canvas.svg.getScreenCTM().inverse());
return { x: _local.x, y: _local.y };
};
I'am presently monitoring a lightly suspicious or inconsistent behavior.
Given a learner bumping on the difficult strokeNum X...
If drawing strokeNum X has 1 or more errors, then strokeNum X final and correct drawing fires the onMistake
event, while the returned datapoints are actually defining a correct line-drawing.
See the line with one cell bordered in blue: the drawingPath is correct but it fires a onMistake
and set var status="ongoing-mistake"
.
There is no way to mark this drawing as correct ?
Note: in my code
onMistake
event returns var status=ongoing-mistake
onCorrectStroke
event returns var status=ongoing
onComplete
event returns var status=complete
EDIT: maybe need to have
onMistakeDraw
onCorrectDraw
onMistake
➡️ onMistakeStroke
onCorrectStroke
onComplete
➡️ onCompleteCharacter
This is a fantastic project! Thanks so much for sharing this.
ZDT somehow used the data to show radical in red, when present. So this is a feature request..
Looking at the stroke file, ZDT may have identified the radical from the prefix information.
For the character 成 (the example on the main zdt page), the stroke data file prefixes are:
1PR
4PO
1NO
3NO
6PO
2NR
7PR
4PR
2PR
I guess the numbers are direction of stroke. Maybe the second character is related to whether or not the final part of a complete stroke is being drawn (P seems to indicate final, whereas N means there are still additional parts remaining to the stroke). But for radical colorizing purposes it looks like the R of the third character stands for Radical.. (With O meaning non-radical) If that is really so, it shouldn't be too hard to also imitate ZDT's radical coloring feature. In the above example, the reason the first stroke contains an R is that the first stroke of the radical (戈) has been joined with the second stroke of the 方 preceeding it. So since they did not record two separate strokes, the entire first stroke will be red. Checked this theory against 你 as well and it works there too.
There are syllabs audios via 1707 numeral tones filenames under CC-by-sa.
Could add audio to stroke order display
First of all, thanks so much for this resource!
I'm trying to get an effect as on the first page of the website, where each character is animated automatically (ie you don't need to press a button), but the animation for that character doesn't begin again until all the characters in the sequence have finished animating - just like the chained animation in the docs, but not requiring a button-press.
I've adjusted the looping code in the docs as follows:
<div class="inline-demo-flex">
<div id="docs-target-5-1" class="inline-demo"></div>
<div id="docs-target-5-2" class="inline-demo"></div>
</div>
<script>
(function() {
var char1 = new HanziWriter('docs-target-5-1', '很', {
width: 100,
height: 100,
padding: 5,
delayBetweenStrokes: 100, // milliseconds
delayBetweenLoops: 5000
});
var char2 = new HanziWriter('docs-target-5-2', '好', {
width: 100,
height: 100,
padding: 5,
delayBetweenStrokes: 100, // milliseconds
delayBetweenLoops: 5000
});
char1.loopCharacterAnimation();
char2.loopCharacterAnimation();
})();
</script>
But this just leaves the two characters looping independently. Is there any way to get them looping sequentially? Thanks.
I noticed that some strokes could then not be rendered with the final color (various shades of gray and not the final black for instance), probably because their opacity animation was stopped before completing (you can experience this on the demo site quite easily).
In some cases they would even stay lingering after end of quiz mode. Note that this might be a second issue. I'm willing to work on that the next days of this week.
The initial frame of stroke animation starts partially filled in, which makes the animation seem less smooth. This is mostly noticeable on small strokes or when drawing very slowly.
I am building a Kanji learning app and I would like to be able to provide stroke data for traditional characters.
Do you know of anywhere online/or a method I could use to could obtain the stroke data for traditional characters in ZDT format?
sorry for the possible noob question - HanziWriter works great for me when I include it using a script tag. but when I try the following in my node.js project
import { HanziWriter } from 'hanzi-writer';
var hanziWriter = new HanziWriter('target-div', '我', {
width: 300,
height: 300,
showOutline: true,
showCharacter: false,
strokeAnimationDuration: 1000,
delayBetweenStrokes: 0,
showHintAfterMisses: 1
});
i get the javascript error
"_hanziWriter.HanziWriter is not a constructor"
I am not sure what I am doing wrong?
The margin between the character's bounding box and the svg bounding box is too large and impose too much dead space. Ex :
Provide a parametter to control this margin or translate would be helpful. 👍
For reference, on Wikimedia Commons and for similar 300x300, we used 15px margins (5% and up) on each side.
Hey! I maintain Make Me a Hanzi, and I discovered your project through someone who uses your library to make use of that data. Just a quick note: since that data is licensed under the original Arphic Public License, it must be distributed with that license and with a note of the copyright.
The license files themselves are here. I've been noting it as two items in other projects (https://www.skishore.me/inkstone/) that use the data, since the data includes both the font and the work I did on top of it:
Arphic PL KaitiM GB and UKai
Copyright 1999 Arphic Technology Co., Ltd.; licensed under the Arphic Public License
http://www.arphic.com.tw/en/home/index
Make Me a Hanzi
Copyright 1999 Arphic Technology Co., Ltd., copyright 2016 Shaunak Kishore; licensed under the Arphic Public License
https://github.com/skishore/makemeahanzi
Great work on this project! It really is a useful library for anyone building a Chinese learning website.
Some strokes flicker, showing themselves as fully drawn for a split second before animation starts in Safari
I tried to input character "乙" in the demo page, it looks like there are some rendering errors, results in blank display.
Is there any way to change the color of a HanziWriter that already exists in the DOM without messing with its internal variables?
Looks like I could make it work by hacking on hw._canvas.svg
/ hw._renderState.state.character.main.strokeColor
, but that doesn't seem like a great idea 😈
Alternatively, looks like I might be able to use CSS to colour the paths instead of relying on a JS way..
svg g g:nth-child(2) path {
stroke: hotpink;
}
Not sure if there's a better way :)
Sorry for this noob question, but how can I get the animation to repeat over and over again?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.